写点什么

TypeScript 中的 Index Signatures

用户头像
Regan Yue
关注
发布于: 刚刚

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_000totalSalary(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_000totalSalary(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' }; // 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' 用于键。


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

发布于: 刚刚阅读数: 3
用户头像

Regan Yue

关注

还未添加个人签名 2020.08.12 加入

对Go、Python、网络安全、区块链感兴趣. · 华为云云享专家 · 掘金资讯创作者

评论

发布
暂无评论
TypeScript 中的 Index Signatures