TypeScript 中的 Index Signatures

TypeScript 中的 Index Signatures

原文链接:dmitripavlutin.com/typescript-…

在这,您用 2 个对象来描述 2 个软件开发人员的工资:

const salary1 = {
  baseSalary: 100_000,
  yearlyBonus: 20_000
};
 
const salary2 = {
  contractSalary: 110_000
};
复制代码

你想实现一个函数,根据salary对象返回总薪酬:

function totalSalary(salaryObject: ???) {
  let total = 0;
  for (const name in salaryObject) {
    total += salaryObject[name];
  }
  return total;
}
totalSalary(salary1); // => 120_000
totalSalary(salary2); // => 110_000
复制代码

How would you annotate the salaryObject parameter of the totalSalary() function to accept objects with string keys and number values?

您将如何给totalSalary()函数的salaryObject参数做注解来达到可以接受具有字符串键和数值的对象?

答案是使用index signature!

Let’s find what are TypeScript index signatures and when they’re needed.

让我们一起了解什么是 TypeScript index signatures并且何时使用它们。

  1. 为什么要使用 index signature

The idea of the index signatures is to type objects of unknown structure when you only know the key and value types.

在您只知道键和值类型时却需要输入未知结构的对象,这时使用index signature的主意就出来了。

An index signature fits the case of the salary parameter: the function should accept salary objects of different structures — only that values to be numbers.

index signature适合上面的工资参数的例子:该函数接受了不同结构的工资对象——但是只有数字值。

Let’s annotate the salaryObject parameter with an index signature:

让我们用index signature给参数salaryObject注解:

function totalSalary(salaryObject: { [key: string]: number }) {
  let total = 0;
  for (const name in salaryObject) {
    total += salaryObject[name];
  }
  return total;
}
 
totalSalary(salary1); // => 120_000
totalSalary(salary2); // => 110_000
复制代码

{ [key: string]: number } is the index signature, which tells TypeScript that salaryObject has to be an object with string type as key and number type as value.

{ [key: string]: number }是index signature,它告诉 TypeScript这个salaryObject必须是一个string类型为键、number类型为值的对象。

Now the totalSalary() accepts as arguments both salary1 and salary2 objects, since they are objects with number values.

现在totalSalary()接受salary1salary2对象作为参数,因为它们都是只有数值的对象。

However, the function would not accept an object that has, for example, strings as values:

但是,该函数不会接受具有例如字符串作为值的对象:

const salary3 = {
  baseSalary: '100 thousands'
};
 
totalSalary(salary3);

Argument of type '{ baseSalary: string; }' is not assignable to parameter of type '{ [key: string]: number; }'.
  Property 'baseSalary' is incompatible with index signature.
    Type 'string' is not assignable to type 'number'.
复制代码
  1. Index signature 的语法

The syntax of an index signature is pretty simple and looks similar to the syntax of a property, but with one difference. Instead of the property name, you simply write the type of the key inside the square brackets: { [key: KeyType]: ValueType }.

index signature的语法非常简单,看起来类似于属性的语法,但有一个区别。您只需将键的类型写在方括号内,而不是属性名称:{ [key: KeyType]: ValueType }

Here are a few examples of index signatures.

以下是索引签名的几个示例。

The string type is the key and value:

这里键和值是string类型:

interface StringByString {
  [key: string]: string;
}
 
const heroesInBooks: StringByString = {
  'Gunslinger': 'The Dark Tower',
  'Jack Torrance': 'The Shining'
};
复制代码

The string type is the key, the value can be a string, number, or boolean:

这个string类型是键,这里值可以是stringnumberboolean类型:

interface Options {
  [key: string]: string | number | boolean;
  timeout: number;
}
 
const options: Options = {
  timeout: 1000,
  timeoutMessage: 'The request timed out!',
  isFileUpload: false
};
复制代码

Options interface also has a field timeout, which works fine near the index signature.

Options接口也有一个 field timeout,它在index signature附近工作正常。

The key of the index signature can only be a string, number, or symbol. Other types are not allowed:

该index signature的键只能是一个stringnumbersymbol。不允许使用其他类型:

interface OopsDictionary {
  [key: boolean]: string;
An index signature parameter type must be 'string', 'number', 'symbol', or a template literal type.
}
复制代码
  1. Index signature 中需要注意的事项

The index signatures in TypeScript have a few caveats you should be aware of.

TypeScript 中的 index signatures 有一些您应该注意的事项。

3.1 不存在的属性

What would happen if you try to access a non-existing property of an object whose index signature is { [key: string]: string }?

如果您尝试访问 index signature 为 { [key: string]: string } 的对象的不存在属性,会发生什么?

As expected, TypeScript infers the type of the value to string. But if you check the runtime value — it’s undefined:

正如预期的那样,TypeScript 将值的类型推断为string. 但是如果你检查运行时的这个值——它是undefined

interface StringByString {
  [key: string]: string;
}
 
const object: StringByString = {};
 
const value = object['nonExistingProp'];
value; // => undefined
 
const value: stringxxxxxxxxxx interface StringByString {  [key: string]: string;} const object: StringByString = {}; const value = object['nonExistingProp'];value; // => undefined const value: stringinterface StringByString {  [key: string]: string;} const object: StringByString = {}; const value = object['nonExistingProp'];value; // => undefined const value: string
复制代码

value variable is a string type according to TypeScript, however, its runtime value is undefined.

根据 TypeScript的语法,value变量应该是string类型,但是,它的运行时的值为undefined.

The index signature simply maps a key type to a value type, and that’s all. If you don’t make that mapping correct, the value type can deviate from the actual runtime data type.

index signature 只是将键类型映射到值类型,仅此而已。如果您使得该映射出现问题,则运行时值类型可能会偏离实际的数据类型。

To make typing more accurate, mark the indexed value as string or undefined. Doing so, TypeScript becomes aware that the properties you access might not exist:

为了使输入更准确,请将索引值标记为stringundefined。这样做,TypeScript 会意识到您访问的属性可能不存在:

interface StringByString {
  [key: string]: string | undefined;
}
 
const object: StringByString = {};
 
const value = object['nonExistingProp'];
value; // => undefined
 
const value: string | undefined
复制代码

3.2 字符串类型和数字类型的键

假设您有一个数字名称字典:

interface NumbersNames {
  [key: string]: string
}
 
const names: NumbersNames = {
  '1': 'one',
  '2': 'two',
  '3': 'three',
  // etc...
};
复制代码

Accessing a value by a string key works as expected:

通过字符串类型的键访问值,值是预期的那样类型:

const value1 = names['1'];      const value1: string
复制代码

Would it be an error if you try to access a value by a number 1?

如果您尝试通过数字1访问值,值会出错吗?

const value2 = names[1];        const value2: string
复制代码

Nope, all good!

没有,一切都好!

JavaScript implicitly coerces numbers to strings when used as keys in property accessors (names[1] is the same as names['1']). TypeScript performs this coercion too.

当用作属性访问器中的键时,JavaScript 隐式地将数字强制转换为字符串(names[1]names['1']相同)。TypeScript 也执行这种强制。

You can think that [key: string] is the same as [key: string | number].

你可以认为[key: string][key: string | number]是一样的.

  1. Index signature vs Record<Keys, Type>

TypeScript has a utility type Record<Keys, Type> to annotate records, similar to the index signature.

TypeScript 有一个utility type Record<Keys, Type>来注解记录,类似于index signature。

const object1: Record<string, string> = { prop: 'Value' }; // OK
const object2: { [key: string]: string } = { prop: 'Value' }; // OK
复制代码

The big question is… when to use a Record<Keys, Type> and when an index signature? At first sight, they look quite similar!

最大的问题是……何时使用 Record<Keys, Type>以及何时使用 index signature?乍一看,他们真的很像!

As you saw earlier, the index signature accepts only string, number or symbol as key type. If you try to use, for example, a union of string literal types as keys in an index signature, it would be an error:

正如您之前看到的,index signature仅接受string,numbersymbol作为键类型。如果您想尝试使用字符串文字类型的联合作为索引签名中的键,则会出现错误:

interface Salary {
  [key: 'yearlySalary' | 'yearlyBonus']: number
An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.
}
复制代码

This behavior suggests that the index signature is meant to be generic in regards to keys.

这种行为表明对于键来说 index signature 是通用的。

But you can use a union of string literals to describe the keys in a Record<Keys, Type>:

但是您可以使用字符串文字的联合来描述 Record<Keys, Type> 中的键:

type SpecificSalary = Record<'yearlySalary'|'yearlyBonus', number>
 
const salary1: SpecificSalary = { 
  'yearlySalary': 120_000,
  'yearlyBonus': 10_000
}; // OK
复制代码

The Record<Keys, Type> is meant to be specific in regards to keys.

Record<Keys, Type>可能只特定对待键。

I recommend using the index signature to annotate generic objects, e.g. keys are string type. But use Record<Keys, Type> to annotate specific objects when you know the keys in advance, e.g. a union of string literals 'prop1' | 'prop2' is used for keys.

我建议使用index signature来注解通用的对象,例如键是string类型。但是当您事先知道键时,应当使用Record<Keys, Type>注解特定对象,例如字符串类型的联合'prop1' | 'prop2' 用于键。

  1. 结论

If you don’t know the object structure you’re going to work with, but you know the possible key and value types, then the index signature is what you need.

如果您不知道将要使用的对象的结构,但知道可能的键和值类型,那么 index signature 就是您所需要的。

The index signature consists of the index name and its type in square brackets, followed by a colon and the value type: { [indexName: KeyType]: ValueType }. KeyType can be a string, number, or symbol, while ValueType can be any type.

index signature 由方括号中的索引名称及其类型组成,后跟一个冒号和值类型:{ [indexName: KeyType]: ValueType }KeyType可以是stringnumber或者symbol类型,而ValueType可以是任何类型。

猜你喜欢

转载自juejin.im/post/7017410361221447687