{[key: string]: any} 在 TypeScript 中是什么意思

{[key: string]: any} 语法是 TypeScript 中的索引签名,当我们事先不知道类型属性的所有名称及其值的形状时使用。 索引签名指定当一个对象被一个字符串索引时,它返回一个 any 类型的值。

// 👇️ function returning index signature
// (a key-value structure with key string and value any)
function getObj(): { [key: string]: any } {
  return { name: 'Tom', age: 30, pizza: true };
}

// 👇️ Interface using index signature
interface Person {
  [index: string]: any;
}

// 👇️ const p1: Person
const p1: Person = { name: 'Tom', age: 30 };

// 👇️ Type using index signature
type Animal = {
  [index: string]: any;
};

const a1: Animal = { name: 'Alfred', age: 3 };

{[key: string]: any} 语法是 TypeScript 中的索引签名,当我们事先不知道类型属性的所有名称和值的形状时使用。

示例中的索引签名意味着当对象被字符串索引时,它将返回任何类型的值。

我们可能还会在示例中看到索引签名 {[key: string]: string}。 它代表一个键值结构,当用字符串索引时返回一个字符串类型的值。

索引签名 {[key: string]: any} 非常宽泛,并没有真正说明索引签名的用途。

让我们看一个更窄的索引签名的示例,以及尝试设置不同类型的值如何导致类型检查器出错。

interface Person {
  [index: string]: string;
}

// ⛔️ Error: Type 'number' is not assignable to type 'string'.
const p1: Person = { name: 'Tom', age: 30 };

示例中的索引签名意味着当一个对象被一个字符串索引时,它返回一个字符串类型的值。

这就是为什么我们在尝试向字符串类型的对象添加一个值为数字类型的属性时出错的原因。

我们可能会尝试使用索引签名覆盖特定属性的类型。

interface Person {
  [index: string]: string;
  // ⛔️ Error: Property 'age' of type 'number'
  // is not assignable to 'string' index type 'string'.
  age: number;
}

但是示例中 age 属性的类型与字符串索引的类型不匹配,所以 TypeScript 给了我们一个错误。

在这种情况下,我们可以将字符串索引的类型设置为联合。

interface Person {
  [index: string]: string | number;
  age: number;
  name: string;
}

// 👇️ const p1: Person
const p1: Person = { name: 'Tom', age: 30, country: 'Chile' };

具有字符串类型索引签名的值的类型是字符串和数字的联合。

age 和 name 属性的值是联合的子类型,因此我们不会收到错误。

尝试向不在联合中的类型添加属性会导致错误。

interface Person {
  [index: string]: string | number;
  age: number;
  name: string;
  // ⛔️ Error: Property 'languages' of type 'string[]' is
  // not assignable to 'string' index type 'string | number'.
  languages: string[];
}

如果字符串键的值在索引签名中设置为 any,则可以向任何类型的对象添加属性,因为任何事物都比 any 更具体。

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

// 👇️ const p1: Person
const p1: Person = {
  name: 'Tom',
  age: 30,
  country: 'Chile',
  languages: ['english', 'spanish'],
};

这将是缩小我们提前知道的某些属性类型的好方法。

例如,如果我尝试向具有字符串类型的对象添加 age 属性,类型检查器将抛出错误,因为它需要一个 number

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

// 👇️ const p1: Person
const p1: Person = {
  name: 'Tom',
  // ⛔️ Error: Type 'string' is not
  // assignable to type 'number'.
  age: 'twenty',
  country: 'Chile',
  languages: ['english', 'spanish'],
};

如果我们需要防止分配给它们的索引,我们还可以将索引签名设置为 readonly

interface ReadonlyObj {
  readonly [index: string]: any;
}

// 👇️ const p1: Person
const p1: ReadonlyObj = {
  name: 'Tom',
};

// ⛔️ Error: Index signature in type 'ReadonlyObj'
//  only permits reading.
p1.name = 'James';

我们将字符串索引的类型设置为 readonly,因此如果我们尝试写入具有字符串键的属性,类型检查器会给我们一个错误。