前言

关键字
noImplicitAny、any、索引器

参考资料

  • 配置 noImplicitAny:链接

    notes

索引器(成员表达式)
这里要介绍的索引器,其实就是成员表达式:对象[值] 使用成员表达式访问属性。
索引器的这种写法,在 JS 中本来就存在,本节重点介绍与索引器相关的一些类型检查问题。

any 类型
any 类型是 TS 根据实际情况推导出来的。
很多情况下,若我们没有指定变量的具体类型,那么它们将被推断为 any 类型。

索引器的类型检查
在 TS 中,默认情况下,不对索引器(成员表达式)做严格的类型检查,如果找不到对应的成员,那么推断它的类型为 any 类型,我们可以使用配置 noImplicitAny开启对隐式 any 的检查。

键的类型
在索引器中,键的类型可以是字符串,也可以是数字。
在 JS 中,所有的成员名本质上都是字符串,如果使用数字作为成员名,会自动转换为字符串。
在 TS 中,如果某个类中使用了两种类型的索引器,要求两种索引器的值类型必须匹配。

索引器的位置
在类中,索引器书写的位置应该在所有成员之前。

索引器的作用

  • 可以为类动态增加成员
  • 可以实现动态的操作类成员

    codes

noImplicitAny

  1. const obj = {
  2. name: "dahuyou",
  3. age: 23,
  4. "my-pid": "3303...",
  5. };
  6. console.log(obj["my-pid"]);
  7. for (const key in obj) {
  8. console.log(key, obj[key]);
  9. }

my-pid 不满足标识符的命名规范,但是不排除这种属性存在的可能,对于 my-pid 属性的访问,我们需要借助索引器来访问。
**obj["my-pid"]**
**obj.my-pid**

image.png

**obj["pid"]**
我们这么写并不会报错,只不过访问到的值是 undefined
因为在 TS 中,默认情况下,不对索引器(成员表达式)做严格的类型检查。
这种写法不会报错的原因:TS 中是含有隐式的 any 类型的,我们采用的这种写法 obj["pid"] 被隐式地推导为 any 类型。

  1. function fn(s) {
  2. // ...
  3. }

很多情况下,若我们没有指定变量的具体类型,那么它们将被推断为 any 类型。此时 fn 函数的参数 s 就被推断为 any 类型。

  1. {
  2. "compilerOptions": {
  3. "target": "es2016",
  4. "module": "commonjs",
  5. "lib": [
  6. "es2016"
  7. ],
  8. "outDir": "./dist",
  9. "strictNullChecks": true,
  10. "strictPropertyInitialization": true,
  11. "removeComments": true,
  12. "noImplicitUseStrict": true,
  13. "noImplicitAny": true
  14. },
  15. "include": [
  16. "./src"
  17. ],
  18. }

使用配置 noImplicitAny开启对隐式 any 的检查

**obj["pid"]**
image.png

当我们开启 noImplicitAny 之后,这些被隐式推断为 any 类型的写法,都是不满足要求的。

索引器类型约束

  1. class User {
  2. constructor (
  3. public name: string,
  4. public age: number
  5. ) {}
  6. sayHello() {
  7. console.log(`My name is ${this.name}.`);
  8. console.log(`I am ${this.age} years old this year.`);
  9. }
  10. }
  11. const user = new User("dahuyou", 23);
  12. user.sayHello();

image.png

image.png

  1. const methodName = "sayHello";
  2. class User {
  3. constructor (
  4. public name: string,
  5. public age: number
  6. ) {}
  7. [methodName]() {
  8. console.log(`My name is ${this.name}.`);
  9. console.log(`I am ${this.age} years old this year.`);
  10. }
  11. }
  12. const user = new User("dahuyou", 23);
  13. user.sayHello(); // √
  14. user[methodName](); // √
  15. user.methodName(); // ×

**[methodName]() { ... }**
如果属性名是从一个动态的变量中读取,那么可以采用索引器的形式 [methodName]() { ... } 来写属性名。
等效:sayHello() { ... }

image.png 如果采用上面这种写法,当我们输入 user. 时,只会智能提示 user.[methodName] image.png 通过智能提示的对比,会发现索引器属性前边的 icon 和常规写法的成员方法前边的 icon 是不一样的。

  1. class User {
  2. [p: string]: string | number | { (): void }
  3. constructor (
  4. public name: string,
  5. public age: number
  6. ) {}
  7. sayHello() {
  8. console.log(`My name is ${this.name}.`);
  9. console.log(`I am ${this.age} years old this year.`);
  10. }
  11. }
  12. const user = new User("dahuyou", 23);
  13. user.a = 1; // √
  14. user.pid = '3303...'; // √

**[p: string]: string | number | { (): void }**
添加索引器的类型约束,必须写在最前面。
属性名是 string 类型
属性值是 string、number、或函数 (): void

如果没有添加索引器的类型约束,那么我们在访问 user.a 时,就会报错:
image.png

上述所写的索引器的类型约束写的比较死
是针对当前 User 类中已有的一些成员(name、age、sayHello)针对性地写的
属性值可以直接使用 any 类型:**[p: string]: any**

键的类型

索引器的 key 可以是字符串或数字,比如数组。

  1. const arr: number[] = [1, 2, 3];
  2. console.log(arr[0], arr["0"]); // => 1 1

**arr[0]****arr["0"]**是等效的

  1. class MyArray {
  2. 0 = 1;
  3. }

**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 类型

image.png

当出现多个索引器约束时,想要避免冲突很简单
方法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 类型,它们是匹配的。