1

我已经使用 Typescript 几个月了,但一直坚持让我的索引签名正常工作。

sensorTestData = [
  {
    altitude: 249.74905877223617
    gas: 4361
    humidity: 53.16487239957308
    pressure: 30.100467823211194
    temperature: 69.0322421875
    timestamp: "2022-02-23 00:10:12.377165"
  }, 
  {
    altitude: 249.5376138919663
    gas: 700
    humidity: 53.15552212315537
    pressure: 30.100694114206632
    temperature: 69.07759374999999
    timestamp: "2022-02-23 00:10:13.574070"
  }
 ]

对象数组如下所示:{ temperature: number; humidity: number; pressure: number; altitude: number; gas: number; timestamp: string; }

我尝试了多种不同的方法来添加 [metric: string]: any 并且如果不使用“any”似乎无法到达任何地方。

这是我在对象数组中使用的函数示例:

const getMax = (metric: string) => {
    return parseFloat(
      Math.max(...sensorTestData.map((obj: {
            temperature: number;
            humidity: number;
            pressure: number;
            altitude: number;
            gas: number;
            timestamp: string;
          }) => obj[metric])).toFixed(2)
    );

  };
  console.log("maxTemp", getMax("temperature"));
  console.log("maxHumidity", getMax("humidity"));```

Error thrown: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ temperature: number; humidity: number; pressure: number; altitude: number; gas: number; timestamp: string; }'.
  No index signature with a parameter of type 'string' was found on type '{ temperature: number; humidity: number; pressure: number; altitude: number; gas: number; timestamp: string; }'.ts(7053)
4

1 回答 1

1

为了使它编译没有错误,您需要metric参数比string. 毕竟,如果您将一些任意值传递stringgetMax()喜欢getMax("oopsieDaisy"),那么您将尝试获取Math.max()一堆undefined值(这可能是一个错误)。编译器错误正在警告您这个问题;编译器不知道任意string. 因此,您需要确保它metricsensorTestData元素的已知键之一。

事实上,它必须是那些值类型已知为的键number;如果你打电话getMax("timestamp"),你会得到Math.max()一堆string值,(这也可能是一个错误)。

如果我们定义这样的Data接口来对应 的元素sensorTestData

interface Data {
  temperature: number;
  humidity: number;
  pressure: number;
  altitude: number;
  gas: number;
  timestamp: string;
}

然后我们想metric成为类型"temperature" | "humidity" | "pressure" | "altitude" | "gas"(这是已知字符串文字键的联合,其值是)。Datanumber

如果我们为此定义一个类型别名:

type KeysForNumericData = "temperature" | "humidity" | "pressure" | "altitude" | "gas"

我们用它来注释类型metric

const getMax = (metric: KeysForNumericData) => {
  return parseFloat(
    Math.max(...sensorTestData.map((obj) => obj[metric])).toFixed(2)
  );
};

然后编译没有错误。当你打电话时你getMax()也会得到类型检查:

getMax("temperature"); // okay
getMax("humidity"); // okay
getMax("oopsieDaisy"); // error!
// Argument of type '"oopsieDaisy"' is not assignable to 
// parameter of type 'KeysForNumericData'.
getMax("timestamp"); // error!
// Argument of type '"timestamp"' is not assignable to 
// parameter of type 'KeysForNumericData'.

这基本上就是答案,除了如果Data发生变化,类型KeysForNumericData不会自动更新。让编译 KeysForNumericData根据Data. 这是一种方法:

type KeysMatching<T, V> = {
  [K in keyof T]-?: T[K] extends V ? K : never
}[keyof T]

type KeysForNumericData = KeysMatching<Data, number>;
//type KeysForNumericData = "temperature" | "humidity" | "pressure" | "altitude" | "gas"

我已经定义了一个实用程序类型,称为KeysMatching<T, V>(在这个答案这个答案以及可能在其他地方解释)它返回T其属性值可分配给的键V
因此,为了获取Data其值可分配给的键number,我们编写KeysMatching<Data, number>.

有几种不同的实现方式KeysMatching。对此的简要说明:我们正在映射该键KT每个属性T[K]的键,我们通过条件类型检查该属性是否可分配给number。如果是这样,我们包含该键 ( K),否则我们排除它(使用type never。该映射类型(在 之前的所有内容[keyof T]KeysMatching<Data, number>评估为{temperature: "temperature", humidity: "humidity", ... timestamp: never}只有timestamp键具有never属性值的东西。然后我们用索引这个映射类型keyof T,产生属性值的联合,在这种情况下是"temperature" | "humidity" | ... | "gas" | never"。因为never是一种不可能的类型,它被吸收到所有联合中(XXX | never相当于XXX),因此结果是所需的键联合。

现在如果Data更改,KeysForNumericData将自动更改为适合:

interface Data {
  temperature: number;
  humidity: number;
  pressure: number;
  altitude: number;
  gas: number;
  timestamp: string;
  latitude: number;
  longitude: number;
  flavor: string;
  glutenFree: boolean;
}

type KeysForNumericData = KeysMatching<Data, number>;
/* type KeysForNumericData = "temperature" | "humidity" | "pressure" | 
     "altitude" | "gas" | "latitude" | "longitude" */

Playground 代码链接

于 2022-02-26T22:37:21.573 回答