先问一个问题,JavaScript有几种数据类型?
number
、string
、boolean
、null
、undefined
、symbol
、bigint
、object
其中 bigint 是 ES2020 新增的数据类型,而早在 TS3.2 时便成为 TS 的标准,其实还有好多 ES+ 标准是 TS 率先提出的,可见 TS 在很多方面是走在了 ES 前列。
TypeScript又新增了多少种数据类型?
any
、unknown
、enum
、void
、never
、tuple
...
其实 TypeScript 更重要的是通过 interface 和 type 赋予了用户自定义数据类型的能力,使数据在流转的过程中始终能轻易被用户掌握。
/** */
来注释 TypeScript 的类型,当我们在使用相关类型的时候就会有注释的提示,这个技巧可以帮助我们节约翻文档或者跳页看注释的时间,在团队内合理的推广使用,能够极大的提高我们的开发效率。
function test (x) {
return x + x;
}
test(1)
test(2)
test(3)
test(4)
const v8 = require('v8-natives');
const { performance, PerformanceObserver } = require('perf_hooks')
function test(x, y) {
return x + y
}
const obs = new PerformanceObserver((list, observer) => {
console.log(list.getEntries())
observer.disconnect()
})
obs.observe({ entryTypes: ['measure'], buffered: true })
performance.mark('start')
let number = 10000000
// 不优化代码
v8.neverOptimizeFunction(test)
while (number--) {
test(1, 2)
}
performance.mark('end')
performance.measure('test', 'start', 'end')
1. 巧用 keyof
const data = {
a: 3,
hello: 'world'
}
function getValue(o, name) {
return o[name]
}
function getValue(o: any, name: string) {
return o[name]
}
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());
};
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 = 'a'
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 = { id?: number; age?: number; name?: string; }
type PartialUser = Partial<User>
// 相当于: type PickUser = { id: number; age: number; }
type PickUser = Pick<User, "id" | "age">
// 相当于: type OmitUser = { age: number; name: string; }
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: ' ' }, // Hey!
};
5.巧用类型约束
在 .tsx 文件中,泛型可能会被当作 jsx 标签
const toArray = <T>(element: T) => [element]; // Error in .tsx file.
加 extends 可破
const toArray = <T extends {}>(element: T) => [element]; // No errors.
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 优秀的练习题
关注「Alibaba F2E」微信公众号把握阿里巴巴前端新动向
文章评论