1. 巧用 keyof
假如我们要实现一个 getValue 函数,作用是根据传入的 object 和 key 获取 object 上 key 所对应的属性值 value,一开始的 JavaScript 版本可能是这样的。
const data = {
a: 3,
hello: 'world'
}
function getValue(o, name) {
return o[name]
}
可以看到这就是 JavaScript 的弊端,我们对传入的 o 和 name 都没有约束,对这一切都是未知的,人的恐惧就源自对未知事物的不确定性。尤其如果这是一个底层类库,我们不知道有多少人去调用,那这个风险就更加增大了,所以我们可能用 TypeScript 这样去改造它。
function getValue(o: any, name: string) {
return o[name]
}
但这样写貌似也有很多问题,函数返回值的类型变成了 any,失去了 TypeScript 类型校验的功能,让类型重新变的不可控,name 类型固然是 string,但还是太宽泛了,实际上,name 的值只能是o的键的并集,而且如果我们将 name 拼写错误,TypeScript 也不会帮我们报错提示。这个时候我们可以使用 keyof 结合泛型来加强 getValue 函数的类型功能。
function getValue<T extends object, K extends keyof T>(o: T, name: K): T[K] {
return o[name]
}
2. 接口智能提示
interface Seal {
name: string;
url: string;
}
interface API {
"/user": { name: string; age: number; phone: string };
"/seals": { seal: Seal[] };
}
const api = <URL extends keyof API>(url: URL): Promise<API[URL]> => {
return fetch(url).then((res) => res.json());
};
借助泛型以及泛型约束,我们可以实现智能提示的功能,不光接口名可以智能提示,接口返回也可以智能提示。当我们输入 api 的时候,其会自动将 API interface 下的所有 key 提示给我们,当我们输入某一个 key 的时候,其会根据 key 命中的 interface 定义的类型,然后给予类型提示。这在统一接口管理方面有很大的用处,可以帮助我们面向接口编程。
3. 巧用类型保护
interface User {
name: string;
age: number;
occupation: string;
}
interface Admin {
name: string;
age: number;
role: string;
}
export type Person = User | Admin;
export const persons: Person[] = [
{
name: 'Max Mustermann',
age: 25,
occupation: 'Chimney sweep'
},
{
name: 'Jane Doe',
age: 32,
role: 'Administrator'
},
{
name: 'Kate Müller',
age: 23,
occupation: 'Astronaut'
},
{
name: 'Bruce Willis',
age: 64,
role: 'World saver'
}
];
export function logPerson(person: Person) {
let additionalInformation: string;
if (person.role) {
additionalInformation = person.role;
} else {
additionalInformation = person.occupation;
}
console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);
}
persons.forEach(logPerson);
我们可以看到,当我们定义了两种 Person 类型:User 和 Admin,而在使用的时候是比较宽泛的 Person,那我们就不能直接使用 User 或者 Admin 的特有属性 role 或者 occupation。因为 TypeScript 没有足够的信息确定 Person 究竟是 User 还是 Admin。
一种方法是使用类型断言,显示的告诉 TypeScript,person 就是 Admin 类型或者就是 User 类型,但是这样做一方面不够优雅,要在每一处都加上断言;另一方面滥用断言也会让我们的代码变得不可控,不能让 TypeScript 帮助我们进行合理的类型推断。像双重断言可以规避掉 TypeScript 的类型检查机制也是与 any 一样,要尽可能去避免的。
正确的做法是使用类型收缩,比如使用 is,in,typeof,instanceof 等,使得 TypeScript 能够 get 到当前的类型,假如 person 上面有 role 属性,TypeScript 就可以推断出 person 就是 Admin 类型,创建类型保护区块,在当前的代码块按照 Admin 类型处理,代码也简洁了很多。
同样是这个例子,我们再改造一下,通过两个函数来判断 person 的具体类型是 Admin 还是 User。但是很不幸,TypeScript 依然不能很智能的知道 person 在第一个代码块里是 Admin 类型,在第二个代码块里是 User 类型。
这个时候我们就要改造一下 isAdmin 和 isUser 的函数返回,创建用户自定义的类型保护函数,显式的告诉 TypeScript,函数返回为 true 时,指定 person 的类型确定为 Admin 或者 User,这样 TypeScript 就知道 person 的确定类型了,这就是类型位词。
4.常用的高级类型
这其实涉及到了类型编程到概念,简而言之,我们平时写代码是对值进行编程,而类型编程是对类型进行编程,可以利用 keyof 对属性做一些扩展,省的我们要重新定义一下接口,造成很多冗余代码。
这些高级类型在日常编程中有非常广泛的使用,尤其 Partial 可以将所有的属性变成可选的,如在我们日常的搜索逻辑,我们可以根据单一条件搜索,也可以根据组合条件搜索。Omit 可以帮助我们复用一个类型,但是又不需要此类型内的全部属性,当父组件通过 props 向下传递数据的时候,可以剔除一些无用的类型。
Record 也是一个比较常用的高级类型,可以帮助我们从 Union 类型中创建新类型,Union 类型中的值用作新类型的属性。当我们拼写错误,或者漏写一些属性,或者加入了没有预先定义的属性进去,TypeScript 都可以给我们很友好的报错提示。
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Required<T> = {
[P in keyof T]-?: T[P];
};
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type Exclude<T, U> = T extends U ? never : T;
type A = Exclude<'x' | 'a', 'x' | 'y' | 'z'>
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type Record<K extends keyof any, T> = {
[P in K]: T;
};
interface User {
id: number;
age: number;
name: string;
};
type PartialUser = Partial<User>
type PickUser = Pick<User, "id" | "age">
type OmitUser = Omit<User, "id">
type AnimalType = 'cat' | 'dog' | 'frog';
interface AnimalDescription { name: string, icon: string }
const AnimalMap: Record<AnimalType, AnimalDescription> = {
cat: { name: '猫', icon: ' '},
dog: { name: '狗', icon: ' ' },
forg: { name: '蛙', icon: ' ' },
};
5.巧用类型约束
在 .tsx 文件中,泛型可能会被当作 jsx 标签 const toArray = <T>(element: T) => [element];
加 extends 可破const toArray = <T extends {}>(element: T) => [element];
TypeScript 还可以给一些缺乏类型定义的第三方库定义类型,找到一些没有 d.ts 声明的开源库,为开源社区贡献声明文件。
学习参考
- https://www.typescriptlang.org/docs/handbook/release-notes/overview.html 官方各个版本文档
- https://github.com/microsoft/TypeScript/projects/9 官方讨论
- https://github.com/microsoft/vscode VS Code 是 TypeScript 编写的,毫无疑问也是学习的好地方
- https://basarat.gitbook.io/typescript/getting-started TypeScript Deep Dive
- https://github.com/typescript-exercises/typescript-exercises TypeScript 优秀的练习题