TypeScript 中的 Index Signatures

举报
Regan Yue 发表于 2021/10/24 12:23:08 2021/10/24
【摘要】 TypeScript 中的 Index Signatures原文链接:https://dmitripavlutin.com/typescript-index-signatures/在这,您用 2 个对象来描述 2 个软件开发人员的工资:const salary1 = { baseSalary: 100_000, yearlyBonus: 20_000}; const salary2 =...

TypeScript 中的 Index Signatures

原文链接:https://dmitripavlutin.com/typescript-index-signatures/

在这,您用 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'.

2. 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.
}

3. 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]是一样的.

4. 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' }; // OKconst 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']: numberAn 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' 用于键。

5. 结论


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可以是任何类型。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。