任意属性

有时候我们希望一个接口允许有任意的属性,可以使用如下方式:

  1. interface Person {
  2. name: string;
  3. age?: number;
  4. [propName: string]: any;
  5. }
  6. let tom: Person = { name: "Tom", gender: "male" };

使用 [propName: string] 定义了任意属性取 string 类型的值。propName 类似于函数的形参,是可以取其他名字的。

一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:

  1. interface Person {
  2. name: string;
  3. age?: number;
  4. [propName: string]: string | number;
  5. }
  6. let tom: Person = { name: "Tom", age: 25, gender: "male" };

同时定义两种任意属性

需要注意的是,一个接口可以同时定义这两种任意属性,但是 number 类型的签名指定的值类型必须是 string 类型的签名指定的值类型的子集,举个例子:

  1. interface C {
  2. [prop: string]: number;
  3. [index: number]: string;
  4. }
  5. // Numeric index type 'string' is not assignable to string index type 'number'.

上面定义是不成立的,因为 index 指定的值类型是 string,而 prop 指定的值类型是 number,string 并不是 number 的子集。
如果换成下面这样,定义就是成立的,因为 Function 是 object 的子集:

  1. interface C {
  2. [prop: string]: object;
  3. [index: number]: Function;
  4. }

同时定义任意属性和其他类型的属性

需要注意的是,一旦定义了任意属性,那么其他属性(确定属性、可选属性、只读属性等)的类型都必须是它的类型的子集

  1. interface Person {
  2. name: string;
  3. age?: number;
  4. [propName: string]: string;
  5. }
  6. let tom: Person = {
  7. name: 'Tom',
  8. age: 25,
  9. gender: 'male'
  10. };
  11. // index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
  12. // index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
  13. // Index signatures are incompatible.
  14. // Type 'string | number' is not assignable to type 'string'.
  15. // Type 'number' is not assignable to type 'string'.

但是,number 类型的任意属性签名不会影响其他 string 类型的属性签名:

  1. type Arg = {
  2. [index: number]: number;
  3. length: string;
  4. };

但是反过来就不一样了,如果接口定义了 string 类型的任意属性签名,它不仅会影响其他 string 类型的签名,也会影响其他 number 类型的签名。这一点可以参考两种任意类型签名并存时,number 类型的签名指定的值类型必须是 string 类型的签名指定的值类型的子集这句话。