关于TS中的“任意属性”

1,477 阅读2分钟

有时候我们希望一个接口允许有任意的属性、TypeScript给我们提供了两种索引签名:字符串和数字。

比如:

interface Person{
    name: string;
    age: number;
    [propName: string]: any;
}

let tom: Person = {
    name: 'Tom',
    gender: 'male'
};

使用 [propName: string] 定义了任意属性取string类型的值

又比如:

interface IPerson {
    name: string;
    age?: number;
    [propName: string]: string;
}

let tom: IPerson = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Index signatures are incompatible.
//     Type 'string | number' is not assignable to type 'string'.
//       Type 'number' is not assignable to type 'string'.

注意了:

1. 一旦定义了任意属性 且 任意属性的类型为string时、那么确定属性和可选属性的类型都必须是它的类型的子集

👆🌰任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以报错了。
此时 { name: 'Tom', age: 25, gender: 'male' } 的类型被推断成了 { [x: string]: string | number; name: string; age: number; gender: string; }

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

所以可以改写为:

interface IPerson {
    name: string;
    age?: number;
    [propName: string]: string | number;
}

let tom: IPerson = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

but...要注意👇🌰这样是不会报错的:

interface IArgument {
  [index: number]: number;
  a: number;
  b: string;
  c: Function;
}
// 这样是ok的 ???

3. number类型的任意属性签名不会影响string类型

vs

而👇🌰这样是会报错的:

interface IArgument {
  [index: string]: number;
  a: number;
  b: Function;
}
// 这样是会报错的 ???

因为 b: Function 不是任意属性的值类型(number)的子集、又回到了第一点的约束

看出区别了吗?

那我们可以同时使用两种类型的索引吗? 可以

可以同时使用两种类型的索引,但是数字索引的值类型必须是字符串索引的值类型的子类型。 当使用 number 来索引时,JavaScript会将它转换成 string 然后再去索引对象。 也就是说用 1 去索引等同于使用 "1" 去索引,因此两者需要保持一致。

🌰:

interface ITest{
	[prop: string]: object;
    [index: number]: Function;
}

and

interface ITest{
	[prop: string]: string;
    [index: number]: Function;
}
// Numeric index type 'Function' is not assignable to string index type 'string'.

因此:

两种任意类型签名并存时,number 类型的签名指定的值类型必须是 string 类型的签名指定的值类型的子集

一旦定义了 ‘索引为字符串类型签名’ 的任意属性,那么其他属性(确定属性、可选属性、只读属性等)的类型都必须是它的 ‘值类型’ 的子集。