全部 / 前端 / 技术 · 2024年6月13日 0

TS – Index Signatures

你有 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必须是一个以stringtype作为键和numbertype作为值的对象。

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

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

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 }

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

stringtype是键和值:

interface StringByString {  

	[key: string]: string;  

}  

  

const heroesInBooks: StringByString = {  

	'Gunslinger': 'The Dark Tower',  
	
	'Jack Torrance': 'The Shining'  

};

string类型是键,值可以是stringnumberboolean

interface Options {  

	[key: string]: string | number | boolean;  
	
	timeout: number;  

}  

  

const options: Options = {  

	timeout: 1000,  
	
	timeoutMessage: 'The request timed out!',  
	
	isFileUpload: false  

};

Options接口还有一个字段timeout,它在索引签名附近工作正常。

索引签名的键只能是stringnumbersymbol。不允许其他类型:

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

索引签名将键类型映射到值类型-仅此而已。如果您不正确映射,值类型可能会偏离实际的运行时数据类型。

为了使键入更准确,请将索引值标记为stringundefined。这样做,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>以及什么时候使用索引签名?乍一看,它们看起来很相似!

如您之前所见,索引签名仅接受stringnumbersymbol作为密钥类型。例如,如果您尝试使用字符串文字类型的并集作为索引签名中的键,则会出错:

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

要将键类型限制为特定的字符串并集,则使用Record<Keys, Values>utilty类型是一个更好的主意。索引签名不支持字符串文字类型的并集。