TypeScript中强类型字典的不同方法

122 阅读3分钟

这篇文章介绍了TypeScript中强类型字典的不同方法。字典有时被称为哈希或地图--基本上它是一个键值对的集合。在这篇文章中,我们将关注键值未知的字典--如果我们知道键值,那么可以使用一个类型别名接口

TypeScript Dictionaries

问题是

TypeScript需要在访问对象之前了解其完整表示。例如,下面的代码在JavaScript中是正常的,但在TypeScript中会引发类型错误:

let scores = {};
scores.bill = 10; // 💥 - Property 'bill' does not exist on type '{}'

下面的代码在JavaScript中向控制台输出undefined ,但在TypeScript中引发了类型错误:

let scores = { bill: 10 };
console.log(scores.fred); // 💥 - Property 'fred' does not exist on type '{ bill: number; }'

我们可以在一个类型注解中使用any ,但是这样就不会对字典进行类型检查:

let scores: any = {};
scores.bill = 10; // ✔️ - no type error
scores.invalidProp = true; // ✔️ - no type error

我们希望发生一些类型检查,但又能灵活地在运行时向字典中添加键。

使用索引的对象类型注解

我们可以使用一个索引对象类型注解,如下所示:

let scores: { [name: string]: number } = {};
scores.bill = 10; // ✔️ - no type error
scores.bill = "10"; // 💥 - Type 'string' is not assignable to type 'number'

这里我们指定字典的键是字符串,值是数字。

"name "标签可以是我们喜欢的任何东西。通常使用 "key":

let scores: { [key: string]: number } = {};
scores.bill = 10;

不过标签不能省略:

let scores: { [string]: number } = {};
// 💥 - 'string' only refers to a type, but is being used as a value here

不幸的是,我们不能用联合类型来限制键:

let scores: {
  [name: "bill" | "bob"]: number;
} = {};
// 💥 - An index signature parameter type cannot be a union type. Consider using a mapped object type instead

从一个更积极的角度来看,我们可以有更复杂的值类型:

type Person = {
  email: string;
  rating: number;
};
let scores: { [name: string]: Person } = {};
scores.bill = {
  email: "bill@somewhere.com",
  rating: 9,
};
scores.bob = {
  emailAddress: "bill@somewhere.com",
  // 💥  Type '{ emailAddress: string; rating: number; }' is not assignable to type 'Person'.
  rating: 9,
};

使用Record 实用类型

有一个Record 实用类型,它比索引对象类型更简洁一些。它也允许键的类型是一个联合类型:

let scores: Record<string, number> = {};
scores.bill = 10; // ✔️ - no type error
scores.trevor = "10"; // 💥 - Type 'string' is not assignable to type 'number'

我们可以使用联合类型来缩小键的类型,如下所示。

let scores: Record<"bill" | "bob", number> = {};
scores.bill = 10; // ✔️ - no type error
scores.trevor = 10; // 💥 - Property 'trevor' does not exist on type 'Record<"bill" | "bob", number>'

使用Map

Map是一个标准的JavaScript功能,对于保存键值对非常有用。

对于Map ,有一个相应的TypeScript类型,叫做Map 。这是一个通用类型,它接收键和值的类型作为参数:

let scores = new Map<string, number>();
scores.set("bill", 10);
scores.set("bob", "10"); // 💥 - Argument of type 'string' is not assignable to parameter of type 'number'.

我们可以对键使用联合类型,对值使用对象类型,如下所示。

type Person = {
  email: string;
  rating: number;
};
let scores = new Map<"bill" | "bob", Person>();
scores.set("bill", {
  email: "bill@somewhere.com",
  rating: 9,
});

Map 的一个好处是,它提供了一个很好的API来访问对象中的项目:

let scores = new Map<"bill" | "bob", Person>();
scores.set("bill", {
  email: "bill@somewhere.com",
  rating: 9,
});
scores.set("bob", {
  email: "bob@somewhere.com",
  rating: 9,
});
console.log(scores.has("bill")); // true

scores.forEach((person) => console.log(person));
// { "email": "bill@somewhere.com", "rating": 9 }
// { "email": "bob@somewhere.com", "rating": 9 }

很好!

总结

Record 实用类型是一种简明的方法,可以对字典进行强类型化。如果我们想要一个围绕字典的更好的 API,我们可以使用Map