索引签名的使用及松散索引签名
- 推荐:阅读本文,需要有一定的ts基础,最好有理解
字面量类型严格检测
方面的知识,才能理解奇怪的现象
- 字面量严格检测官方描述:https://github.com/microsoft/TypeScript/pull/3823
索引签名基本使用
interface IIterator {
// 索引签名
[index: string]: number;
// 该属性受索引签名约束,
// 如果返回其他类型,则会报错
length: number;
}
type IteratorType = {
[index: number]: string;
// 这里index为number,这里没报错的原因是,数组默认原型包含length字段
length: number;
};
const test1: IIterator = {
age: 23,
price: 18.9,
length: 2,
};
const test2: IteratorType = ["1", "2"];
js中数组下表会转成string类型
原文(官方文档):JavaScript will actually convert that to a
string
before indexing into an object. That means that indexing with100
(anumber
) is the same thing as indexing with"100"
(astring
), so the two need to be consistent.文档链接:https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures
翻译:js实际上会在索引到对象之前将其转换为字符串,也就死说100(number)进行索引与"100"(string)进行索引是一回事,因此两者(两种索引签名)需要保持一致。
同时包含两种(number/string)的索引签名
class Animal {
constructor(public age: number) {}
}
class Pig extends Animal {
constructor(public name: string, age: number) {
super(age);
}
}
interface IBothIndexIterator {
[index: number]: Pig; // 索引类型为number的返回值类型,必须为必须为Animal or Animal的子类型
[index: string]: Animal;
}
// ❌ 错误演示
interface IErrorBothIndexIterator {
[index: number]: object; // ❌ 索引类型为number的返回值类型不是Animal,反而是Animal的父类型
[index: string]: Animal;
}
奇怪的现象:为什么返回值为any时,不在校验"数组"的索引类型了?
- 默认所有的索引签名都是严格的字面量检测,不符合条件的都会报错!
- 也就是说,即使js会将number covert to string(0 -> “0”),但是字面量严格检测只会将它视为number类型!
// 索引签名数组问题 - [index: string]: string
interface IIndex {
[index: string]: string;
}
// 尝试将arr改为非fresh新鲜的,扩展字面量
const arr = ["1", "2"];
// ❌ 字面量严格类型检测,索引默认为number无法通过
const arr1: IIndex = arr;
// ❌ 同上错误
const arr2: IIndex = ["1", "2"];
// ✅ 符合类型
const obj1: IIndex = {
"0": "0",
"2": "1",
};
// ✅ 符合类型,不要以为写的0就是个number了,实际上object的key都是string类型,所以这里通过
const obj2: IIndex = {
0: "0",
1: "1",
};
这里都很好理解,我要讲的是下面这个现象,类型检测居然不会检测索引类型了?🤔
奇怪的现象🤔
// 奇怪的现象:[index: string]: any;
interface IIndexInattentive {
[index: string]: any;
}
// ✅ 都通过🤔
const arr1: IIndexInattentive = ["1", "2"];
const obj1: IIndexInattentive = {
0: "0",
1: "1",
};
这里就仿佛根本没有对索引进行检索
论点
:是否因为字面量包含了函数等方法,导致不通过呢?❌
// 奇怪的现象论点:字面量隐式包含函数类型 ❌
interface IIndexSigWithFunc {
[index: string]: string | ((...args: any[]) => any);
}
// ❌
const arr1: IIndexSigWithFunc = ["1", "2"];
// ✅
const obj1: IIndexSigWithFunc = {
"0": "0",
"2": "1",
"3"() {},
};
破案奇怪的现象
- 在官方issue我找到了答案,首先是官方成员的答复与链接https://github.com/microsoft/TypeScript/issues/24083#issuecomment-390710662
- 官方issue的会议讨论/谈话:
I think that's a good assumption, I can't think of a scenario where the target type would want to mandate an index signature explicitly. The index signature of type of any or empty object would then always signify a loose check, making the above code pass without error - correct?
翻译:我认为这是一个很好的假设,我想不出目标类型想要明确要求索引签名的场景。然后,
any
类型或空对象
的索引签名将始终表示松散的检查,从而使上述代码无错误地通过
- 由官方的描述可以得出以下宽松索引签名的触发条件
- 索引签名返回(指向)
any
类型 - 对于
空对象
的索引签名默认为宽松
- 索引签名返回(指向)
// 奇怪的现象:[index: string]: any; 宽松索引签名
// 方式一:any触发条件的宽松索引签名
interface IIndexInattentive {
[index: number]: any;
}
// ✅ 你会发现它们不再检查索引类型,完全不检查了
const arr1: IIndexInattentive = ["1", "2"];
const obj1: IIndexInattentive = {
0: "0",
1: "1",
[Symbol("2")]: () => {},
};
// 方式二:空对象触发条件的索引签名
// ✅ 都通过
let obj3: {} = ["1", "2", 3, true];
obj3 = {
k: "v",
"0": 0,
1: "1",
// 甚至symbol都能通过
[Symbol("3")]: () => {},
};
总结:宽松索引签名,是为了特定场景,如框架的类型定义,不用因为过于严格,而导致类型定义起来特别麻烦
参考文档
- 官方issue字面量严格检测官方描述:https://github.com/microsoft/TypeScript/pull/3823
- 官方issue索引签名针对于any类型过于宽松:https://github.com/microsoft/TypeScript/issues/24083
- 官方issue松散的索引签名的目的:https://github.com/microsoft/TypeScript/pull/3823#issuecomment-125595400
- 官方issue松散的索引会议谈话:https://github.com/microsoft/TypeScript/pull/3823#issuecomment-125695027
- 官方issue放宽索引签名,允许松散索引签名:https://github.com/microsoft/TypeScript/pull/4074