前言
在开发中使用 TypeScript 已经一年多了,逃不开“真香定律”哈,日常重度依赖 TypeScript。在组件开发、前后端对接或者定位问题的过程中,类型检查发挥了极大的作用,可以说提高生产力了。这里就总结一些个人的常用方法吧,也算备忘录了。
类型拓展
interface 继承 interface
interface A {
a: number;
}
interface B {
b: number;
}
interface C extends A, B {
c: number;
}
interface 继承 type
interface A {
a: number;
}
type B = { b: number };
interface C extends A, B {
c: number;
}
type 交叉类型
type A = { a: number };
type B = { b: number };
type C = A & B & { c: number };
定义函数类型
interface 语法
interface Func {
(params: number): number;
}
type 语法
type Func = (params: number) => number;
联合类型
interface 语法及解释
interface A {
a: number;
c: number;
}
interface B {
b: number;
c: string;
}
// 只取有交叉的值 type C = {c: number};
type C = A | B;
type 语法及解释
type A = {
a: number;
c: number;
};
type B = {
b: number;
c: string;
};
// 真正的 union, type C = {a: number; c: number} | {b: number; c: number}
type C = A | B;
type D = string | number;
工具泛型
Partial<T>
将接口所有属性置为可选
// 实现
type Partial<T> = {
[P in keyof T]?: T[P];
};
Required<T>
将接口所有属性置为必选
// 实现
type Required<T> = {
[P in keyof T]-?: T[P];
};
Readonly<T>
将接口所有属性置为只读
// 实现
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
Pick<T, K>
从接口 T 中选取所需的键值
// 实现
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type A = { a: number; b:number };
type B = Pick<A, "b">; // {b: number}
Record<K, T>
返回键值为 K,属性值为 T 的类型
// 实现
type Record<K extends keyof any, T> = {
[P in K]: T;
};
type B = Record<"a" | "b", number>;//{ a: number; b:number }
Record<K, T>
返回键类型为 K,属性值为 T 的类型
// 实现
type Record<K extends keyof any, T> = {
[P in K]: T;
};
type B = Record<"a" | "b", number>;//{ a: number; b:number }
Exclude<T, U>
从 T 中选取不在 U 中的类型
// 实现
type Exclude<T, U> = T extends U ? never : T;
// 用例
type A = { a: number; b:number };
type B = { b: number }
type C = Exclude<keyof A, keyof B>; // "a"
Extract<T, U>
和 Exclude 相反,从 T 中选取在 U 中的类型
// 实现
type Extract<T, U> = T extends U ? T : never;
// 用例
type A = { a: number; b:number };
type B = { b: number }
type C = Extract<keyof A, keyof B>; // "b"
Omit<T, K>
从类型 T 中,移除指定键值的属性
// 实现
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
NonNullable<T>
保证类型不为 null 或者 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
Parameters<T>
得到函数的参数类型数组,在请求接口时很常用的一个方法
// 实现
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
ReturnType
得到函数的返回类型,同样也是在请求接口时很常用的一个方法
// 实现
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
类型推断
我们知道 TypeScript 支持自动类型推断
自动类型推断
typeof
type A = number | string;
let a: A = 0;
if (typeof a === 'number') {
a++;
}
instanceof
let a = 1 > 2 ? new Number(1) : new String('a');
if (a instanceof Number) {
a = a.toFixed(2);
}
- 直接声明
let a = 1; // number
let b = "b; // string
//...
- 浏览器内置的方法
let dom = document.createElement('a');//HTMLAnchorElement
自定义类型推断
有时在代码中我们可能会写一些“ducking”模型的接口,也就是说,当一个动物能发出鸭叫,我们就认为它是鸭子,这个时候 TypeScript 是无法自动推断类型的,因此需要我们明确指定推导得到的是哪个类型,以免类型报错
//示例,我们规定一个元素是 HTMLCanvasElement 只要它拥有 toBlob 方法
function isCanvasElement(el: HTMLElement): el is HTMLCanvasElement {
return !!(el as HTMLCanvasElement).toBlob;
}
其他技巧
TypeScript 有什么高级技巧呢?其实也没啥特别的,就是利用提供的语法进行组合得到新的类型。
LiteralUnion<T, U>
有时候我们需要定义一些字面量的联合类型,同时我们也想要在编写代码的过程中能够自动补全
type LiteralUnion<T extends U, U> = T | (U & {_: never});
//无自动补全,因为 success 等字面量都 extends 自 string,会被 string 覆盖
type MessageType = 'success' | 'error' | 'warning' | string;
let msg: LiteralUnion<MessageType, string> = "success";
infer
通过 infer 指定我们要推导的类型
// <number>[]
type ElementOf<T> = T extends (infer E)[] ? E : never;
let nums = [0, 1, 2];
type Element = ElementOf<typeof nums>;//number
// Promise<number>
type PromiseOf<T> = T extends Promise<infer E> ? E : never;
let promise = Promise.resolve(0);
type PromiseReturnType = PromiseOf<typeof promise>; //number
函数重载
ts 中的函数重载并不意味着可以重复声明两个同名的函数,而是可以声明两个参数/返回值不同的函数类型,同时函数实现要跟在声明重载函数之后
//示例
function count(a: number, b: number): number;
function count(a: number): number;
function count(...nums: number[]) {
if (nums.length >= 2) {
return nums.reduce((a, b) => a + b, 0);
} else {
return nums[0] * 2;
}
}
总结
github 上也有很多优秀的ts库,比如 utility-types、type-fest。进一步学习可以从研究这些源码开始。