你有 2 个描述,软件开发人员薪水的对象:
const salary1 = {
baseSalary: 100_000,
yearlyBonus: 20_000
};
const salary2 = {
contractSalary: 110_000
};
您想实现一个根据工资对象返回总薪酬的函数:
function totalSalary(salaryObject: ???) {
let total = 0;
for (const name in salaryObject) {
total += salaryObject[name];
}
return total;
}
console.log(totalSalary(salary1)); // => 120_000
console.log(totalSalary(salary2)); // => 110_000
您将如何注释totalSalary()
函数的salaryObject
参数以接受键为字符串、值为数字的对象?
答案是使用索引签名!
让我们找到什么是TypeScript索引签名以及何时需要它们。
1.为什么要索引签名
索引签名的思想是在您只知道键和值类型时键入未知结构的对象。
索引签名适合薪水参数的情况:该函数应该接受不同结构的薪水对象-只需确保对象值是数字。
让我们用索引签名注释salaryObject
参数:
function totalSalary(salaryObject: { [key: string]: number }) {
let total = 0;
for (const name in salaryObject) {
total += salaryObject[name];
}
return total;
}
console.log(totalSalary(salary1)); // => 120_000
console.log(totalSalary(salary2)); // => 110_000
{ [key: string]: number }
是索引签名,它告诉TypeScriptsalaryObject
必须是一个以string
type作为键和number
type作为值的对象。
现在totalSalary()
接受salary1
和salary2
对象作为参数,因为它们是具有数字值的对象。
但是,该函数不会接受具有例如字符串作为值的对象:
const salary3 = {
baseSalary: '100 thousands'
};
// Type error:
// 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'.
totalSalary(salary3);
2.索引签名语法
索引签名的语法很简单,看起来类似于属性的语法。但有一个区别:将键的类型写在方括号内:{ [key: KeyType]: ValueType }
。
以下是索引签名的几个示例。
string
type是键和值:
interface StringByString {
[key: string]: string;
}
const heroesInBooks: StringByString = {
'Gunslinger': 'The Dark Tower',
'Jack Torrance': 'The Shining'
};
string
类型是键,值可以是string
、number
或boolean
:
interface Options {
[key: string]: string | number | boolean;
timeout: number;
}
const options: Options = {
timeout: 1000,
timeoutMessage: 'The request timed out!',
isFileUpload: false
};
Options
接口还有一个字段timeout
,它在索引签名附近工作正常。
索引签名的键只能是string
、number
或symbol
。不允许其他类型:
interface OopsDictionary {
// Type error:
// An index signature parameter type must be 'string', 'number',
// 'symbol', or a template literal type.
[key: boolean]: string;
}
3.索引签名警告
TypeScript中的索引签名有一些您应该注意的注意事项。
3.1不存在的财产
如果您尝试访问索引签名为{ [key: string]: string }
的对象的不存在属性会发生什么?
正如预期的那样,TypeScript将值的类型推断为string
。但是如果您检查运行时值-它是undefined
:
interface StringByString {
[key: string]: string;
}
const object: StringByString = {};
const value = object['nonExistingProp'];
console.log(value); // => undefined
value
根据TypeScript,变量是string
类型,但是,它的运行时值是undefined
。
索引签名将键类型映射到值类型-仅此而已。如果您不正确映射,值类型可能会偏离实际的运行时数据类型。
为了使键入更准确,请将索引值标记为string
或undefined
。这样做,TypeScript会意识到您访问的属性可能不存在:
interface StringByString {
[key: string]: string | undefined;
}
const object: StringByString = {};
const value = object['nonExistingProp'];
console.log(value); // => undefined
3.2字符串和数字键
假设您有一本数字名称字典:
interface NumbersNames {
[key: string]: string
}
const names: NumbersNames = {
'1': 'one',
'2': 'two',
'3': 'three',
// etc...
};
通过字符串键访问值按预期工作:
const value1 = names['1']; // OK
如果您通过数字1
访问值会出错吗?
const value2 = names[1]; // OK
没有,都很好!
当在属性访问器中用作键时,JavaScript会将数字隐式强制转换为字符串(names[1]
与names['1']
相同)。TypeScript也执行这种强制。
您可以认为[key: string]
与[key: string | number]
相同。
4.索引签名与记录
TypeScript有一个实用程序类型Record<Keys, Values>
来注释记录,类似于索引签名。
const object1: Record<string, string> = { prop: 'Value' }; // OK
const object2: { [key: string]: string } = { prop: 'Value' }; // OK
最大的问题是…什么时候使用Record<Keys, Values>
以及什么时候使用索引签名?乍一看,它们看起来很相似!
如您之前所见,索引签名仅接受string
、number
或symbol
作为密钥类型。例如,如果您尝试使用字符串文字类型的并集作为索引签名中的键,则会出错:
interface Salary {
// Type error:
// An index signature parameter type cannot be a literal type or generic type.
// Consider using a mapped object type instead.
[key: 'yearlySalary' | 'yearlyBonus']: number
}
这种行为表明_索引签名在键方面是通用的。_
但是您可以使用字符串文字的并集来描述Record<Keys, Values>
中的键:
type SpecificSalary = Record<'yearlySalary'|'yearlyBonus', number>
type GenericSalary = Record<string, number>
const salary1: SpecificSalary = {
'yearlySalary': 120_000,
'yearlyBonus': 10_000
}; // OK
如果您想将键限制为特定字符串的并集,则Record<'prop1' | 'prop2' | ... | 'propN', Values>
是替代索引签名的方法。
5.结论
当您不知道对象的确切结构,但您知道键和值类型时,索引签名注释非常适合这种情况。
索引签名由方括号中的索引名称及其类型组成,后跟冒号和值类型:{ [indexName: Keys]: Values }
。Keys
可以是string
、number
或symbol
,而Values
可以是任何类型。
要将键类型限制为特定的字符串并集,则使用Record<Keys, Values>
utilty类型是一个更好的主意。索引签名不支持字符串文字类型的并集。