前言
关键字
noImplicitAny、any、索引器
参考资料
- 配置 noImplicitAny:链接
notes
索引器(成员表达式)
这里要介绍的索引器,其实就是成员表达式:对象[值]
使用成员表达式访问属性。
索引器的这种写法,在 JS 中本来就存在,本节重点介绍与索引器相关的一些类型检查问题。
any 类型
any 类型是 TS 根据实际情况推导出来的。
很多情况下,若我们没有指定变量的具体类型,那么它们将被推断为 any 类型。
索引器的类型检查
在 TS 中,默认情况下,不对索引器(成员表达式)做严格的类型检查,如果找不到对应的成员,那么推断它的类型为 any 类型,我们可以使用配置 noImplicitAny
开启对隐式 any 的检查。
键的类型
在索引器中,键的类型可以是字符串,也可以是数字。
在 JS 中,所有的成员名本质上都是字符串,如果使用数字作为成员名,会自动转换为字符串。
在 TS 中,如果某个类中使用了两种类型的索引器,要求两种索引器的值类型必须匹配。
索引器的位置
在类中,索引器书写的位置应该在所有成员之前。
索引器的作用
noImplicitAny
const obj = {
name: "dahuyou",
age: 23,
"my-pid": "3303...",
};
console.log(obj["my-pid"]);
for (const key in obj) {
console.log(key, obj[key]);
}
my-pid 不满足标识符的命名规范,但是不排除这种属性存在的可能,对于 my-pid 属性的访问,我们需要借助索引器来访问。**obj["my-pid"]**
✅**obj.my-pid**
❎
**obj["pid"]**
✅
我们这么写并不会报错,只不过访问到的值是 undefined
因为在 TS 中,默认情况下,不对索引器(成员表达式)做严格的类型检查。
这种写法不会报错的原因:TS 中是含有隐式的 any 类型的,我们采用的这种写法 obj["pid"]
被隐式地推导为 any 类型。
function fn(s) {
// ...
}
很多情况下,若我们没有指定变量的具体类型,那么它们将被推断为 any 类型。此时 fn 函数的参数 s 就被推断为 any 类型。
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"lib": [
"es2016"
],
"outDir": "./dist",
"strictNullChecks": true,
"strictPropertyInitialization": true,
"removeComments": true,
"noImplicitUseStrict": true,
"noImplicitAny": true
},
"include": [
"./src"
],
}
使用配置 noImplicitAny
开启对隐式 any 的检查
**obj["pid"]**
❎
当我们开启 noImplicitAny
之后,这些被隐式推断为 any 类型的写法,都是不满足要求的。
索引器类型约束
class User {
constructor (
public name: string,
public age: number
) {}
sayHello() {
console.log(`My name is ${this.name}.`);
console.log(`I am ${this.age} years old this year.`);
}
}
const user = new User("dahuyou", 23);
user.sayHello();
const methodName = "sayHello";
class User {
constructor (
public name: string,
public age: number
) {}
[methodName]() {
console.log(`My name is ${this.name}.`);
console.log(`I am ${this.age} years old this year.`);
}
}
const user = new User("dahuyou", 23);
user.sayHello(); // √
user[methodName](); // √
user.methodName(); // ×
**[methodName]() { ... }**
如果属性名是从一个动态的变量中读取,那么可以采用索引器的形式 [methodName]() { ... }
来写属性名。
等效:sayHello() { ... }
如果采用上面这种写法,当我们输入
user.
时,只会智能提示user.[methodName]
通过智能提示的对比,会发现索引器属性前边的 icon 和常规写法的成员方法前边的 icon 是不一样的。
class User {
[p: string]: string | number | { (): void }
constructor (
public name: string,
public age: number
) {}
sayHello() {
console.log(`My name is ${this.name}.`);
console.log(`I am ${this.age} years old this year.`);
}
}
const user = new User("dahuyou", 23);
user.a = 1; // √
user.pid = '3303...'; // √
**[p: string]: string | number | { (): void }**
添加索引器的类型约束,必须写在最前面。
属性名是 string 类型
属性值是 string、number、或函数 (): void
如果没有添加索引器的类型约束,那么我们在访问 user.a 时,就会报错:
上述所写的索引器的类型约束写的比较死
是针对当前 User 类中已有的一些成员(name、age、sayHello)针对性地写的
属性值可以直接使用 any 类型:**[p: string]: any**
键的类型
索引器的 key 可以是字符串或数字,比如数组。
const arr: number[] = [1, 2, 3];
console.log(arr[0], arr["0"]); // => 1 1
**arr[0]**
和 **arr["0"]**
是等效的
class MyArray {
0 = 1;
}
**0 = 1**
在 TS 中我们可以直接这么写,表示一组键值对,key 为 0,value 为 1。
在 JS 中,索引器的 key 要求是 string、symbol 类型,如果直接写数字作为 key,是会报错的。
class MyArray {
constructor() {
this[0] = 1;
}
}
class MyArray {
[index: number]: string
0 = "000";
1 = "111";
2 = "222";
}
const arr = new MyArray();
arr[0] = "123";
arr[4] = "444";
console.log(arr);
// => MyArray { '0': '123', '1': '111', '2': '222', '4': '444' }
arr[0] = "123"
动态修改已有的值arr[4] = "444"
动态添加新的值
[index: number]: string
虽然添加了索引器约束,要求 key 是 number 类型,value 是 string 类型,但是实际上,上述写法在 js 中,key 都会转换为 string 类型。从输出结果中不难看出这一点。
export {};
class A {
[prop: number]: string
}
const a = new A();
a[0] = "111";
a["0"] = "222"; // √
console.log(a["0"]); // => 222
a["0"] = "222"
因为 TS 知道,这些代码编译后会转为 JS,所以我们这么写,它是不会报错的。即便看似与我们写的索引器类型不匹配。
a[0]
和 a["0"]
两者是等效的。
class A {
[prop: number]: string // ×
[prop: string]: number // √
}
在 TS 中,如果某个类中使用了两种类型的索引器,要求两种索引器的值类型必须匹配。
上面提到:JS 中,我们访问 a[0]
和 a["0"]
都是等效的,这其实就说明,我们写的 [prop: string]: number
和 [prop: number]: string
也是等效的。
冲突
第二个索引器要求,value 是一个 number 类型;
第一个索引器要求,value 是一个 string 类型;
显然 value 不可能同时是 number、string 类型
当出现多个索引器约束时,想要避免冲突很简单
方法1:JS 中的 key 不仅仅可以是 string 类型,还可以是 symbol 类型
方法2:多个索引器之间 value 的类型相互匹配
class A {
[prop: number]: string // √
[prop: symbol]: number // √
}
如果 key 是字符串,那么要求值必须是 string
如果 key 是 symbol,那么要求值必须是 number
class B {}
class A {
[prop: number]: B
[prop: string]: object
}
如果 key 是 string 类型,那么要求值必须是 object。
B 也是 object 类型,它们是匹配的。