本文会给大家介绍一下 TS 中内置的高级类型,并从这些高级类型的源码中学习如何通过 泛型 和 操作符 推导类型。未来如果我们想要扩展一些 TS 中不存在的类型,也可以自行使用泛型和操作符来计算出任何我们想定义的类型。
在了解这些类型之前,需要先认识下 TS 中的操作符和泛型。
操作符
& (Intersection Types )
& 操作符是交集类型,一般是通过 type 方式定义类型时,用来继承使用。若两个类型会冲突,此时使用 & 则得到的类型就会产生冲突,赋任何值都会报错。
type 通过 & 方式去继承属性
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
// 等价于 { color: string; radius: number; }
type ColorfulCircle = Colorful & Circle;
// Error, 缺少 'radius' 属性。
const colorfull: ColorfulCircle = {
color: 'tee',
}
// 等价于 { a: number; b: string;}
type Combined = { a: number } & { b: string };
// Error: 缺少属性 'b'
const com: Combined = { a: 2323};
使用 & 类型冲突时
// 冲突,此时 a 既是 number 又是 string,最后得到的是 { a: never }
type Conflicting = { a: number } & { a: string };
// Error: Type 'number' is not assignable to type 'never'
const con: Conflicting = { a: 12 };
在 TypeScript 中,never 类型表示的是那些永不存在的值的类型。 例如, never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。
另外,需要注意的是,没有类型是 never 的子类型或可以赋值给 never 类型(除了 never 本身之外)。 即使 any 也不可以赋值给 never。
使用 & 排除 null 和 undefined
// string
type T0 = string & {};
// never
type T0 = null & {};
// never
type T0 = undefined & {};
type NonNullable<T> = T & {};
// T1 = string
type T1 = NonNullable<string | null | undefined>;
extends(继承)
extends 可以用来继承一个类,也可以用来继承一个 interface,还可以用来判断有条件类型:
如下面的条件判断例子:
T extends U ? X : Y;
上面代码表达含义是,如果 T 能赋值给 U,那么类型是 X,否则是 Y。(子类型是可以赋值给父类型的,但是父类型不一定能赋值给子类型。)
举个例子:
interface Parent {
x: number;
}
interface Child1 extends Parent {
kind: string;
}
interface Child2 extends Parent {
kind?: string;
}
// 父类型 Parent 无法赋值给 Child1,因为 Child1 还需要一个 kind 属性,而父类型 Parent 没有。所以 Parent extends Child1 为 false
// 但是 Child1 类型可以赋值给 Parent, 因为 Child1 一定有 x 属性。所以 Child1 extends Parent 为 true。
type T0 = Parent extends Child1 ? true : false; // false
type T1 = Child1 extends Parent ? true : false; // true
// 类似的
type T2 = 1 extends number ? true : false; // true
type T3 = number extends 1 ? true : false; // false
type T4 = 'hih' extends string ? true : false; // true
type T5 = string extends 'hih' ? true : false; // false
typeof
在 js 中 typeof 是用来判断一个变量的基础数据类型,在 TS 中,它还有一个作用,就是获取一个变量的声明类型,如果不存在,则获取该类型的推导类型。
interface Person {
name: string;
age: number;
location?: string;
}
const jack: Person = { name: 'jack', age: 100 };
type Jack = typeof jack; // -> Person
function foo(x: number): Array<number> {
return [x];
}
type F = typeof foo; // -> (x: number) => number[]
Jack 这个类型别名实际上就是 jack 的类型 Person,而 F 的类型就是 TS 自己推导出来的 foo 的类型 (x: number) => number[]。
keyof
keyof 操作符可以用于获取某种类型的所有键,其返回类型是联合类型。
interface Person {
name: string;
age: number;
location?: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ...
如果一个类型是字符串或数字索引签名,那么 就会返回这些类型:
type Arrayish = { [n: number]: unknown };
type A = keyof Arrayish; // number
type Mapish = { [k: string]: boolean };
type M = keyof Mapish; // string | number
// 注意:An index signature parameter type must be
// 'string', 'number', 'symbol', or a template literal type
因为 js 中对象 key 总是被转换为 string 类型,所以 obj[0] 和 obj['0'] 是一样的。这里的 M 类型就是 string | number。
in
in 可以遍历枚举类型
type Keys = "a" | "b";
type Obj = {
[p in Keys]: any
} // -> { a: any, b: any }
enum Operate_Type {
ADD ,
DELETE,
UPDATE,
}
type Obj = {
[p in Operate_Type]: string;
} // { 0: string; 1: string; 2: string; }
上面 in 遍历 Keys,并为每个值赋予 any 类型。
infer
在条件类型语句中, 可以用 infer 声明一个类型变量并且对它进行使用。
我们可以用它获取函数的返回类型,代码如下:
// 正常声明一个函数
// (...args: any[]) => any;
type ReturnType<T> = T extends (
...args: any[]
) => infer R
? R
: any;
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
async function stringPromise() {
return "Hello, Semlinker!";
}
interface Person {
name: string;
age: number;
}
async function personPromise() {
return { name: "Semlinker", age: 30 } as Person;
}
type PromiseType<T> = (args: any[]) => Promise<T>;
type UnPromisify<T> = T extends PromiseType<infer U> ? U : never;
// string
type extractStringPromise = UnPromisify<typeof stringPromise>;
// Person
type extractPersonPromise = UnPromisify<typeof personPromise>;
其实这里的 infer R 就是声明一个变量来承载传入函数签名的返回值类型, 简单说就是用它取到函数返回值的类型方便之后使用。
泛型
泛型定义
function identity<T>(value: T) : T {
return value;
}
console.log(identity<number>(1)); // 1
当我们调用 identity<number>(1) ,number 类型就像参数 1 一样,它将出现 T 的任何位置填充该类型。上面代码中 <T> 内部的 T 被称为类型变量,它是我们希望传递给 identity 函数的类型占位符,同时它被分配给 value 参数用来代替它的类型:此时 T 充当的是类型,而不是特定的 number 类型。
其中 T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。除了 T 之外,以下是常见泛型变量代表的意思:
- K(Key):表示对象中的键类型;
- V(Value):表示对象中的值类型;
- E(Element):表示元素类型。
其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U,用于扩展我们定义的 identity 函数:
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity<number, string>(68, "shuai"));
除了显示设定类型变量值之外,还可以省略尖括号内容,让编译器自动选择这些类型,如:
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity(68, "shuai"));
// 此时第一个参数 68 是 number,第二个参数"shuai" 是字符串类型,
// 编译器就会自动将 number 赋给第一个参数 value 的类型 T,
// 将 string 赋给第二个参数 message 的类型 U。
泛型约束
有时我们可能希望限制每个类型变量接受的类型范围,这就是泛型约束的作用,一般通过 extends 来实现约束。举个例子:
一、确保属性存在
function identity<T>(arg: T): T {
console.log(arg.length); // Error, 此时 arr 不一定有 length 属性
return arg;
}
在这种情况下,编译器不会知道 T 确实含有 length 属性,尤其是在可以将任何类型赋给类型变量 T 的情况下。我们需要做的就是让类型变量 extends 一个含有我们所需属性的接口,比如这样:
interface Length {
length: number;
}
function identity<T extends Length>(arg: T): T {
console.log(arg.length); // 可以获取length属性
return arg;
}
// Error: Argument of type 'number' is not assignable to parameter of type 'Length'
// 68 不能赋给类型 Length
identity(68); // Error
T extends Length 用于告诉编译器,我们支持已经实现 Length 接口的任何类型。之后,当我们使用不含有 length 属性的对象作为参数调用 identity 函数时,TypeScript 会提示相关的错误信息。
二、检查对象上的键是否存在
通过前面可以了解到,通过 keyof 可以获取指定类型的所有键。这里可以通过 K extends keyof T 来检查对象上的键是否存在,如:
enum Operator {
ADD,
UPDATE,
DELETE,
}
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
let info = {
name: "Typescript",
operate: Operator.ADD
}
let operate: Operator = getProperty(info, 'operate'); // OK
// Argument of type '"title"' is not assignable to parameter of type '"operate" | "name"'
let supersetOf: string = getProperty(info, 'title'); // Error
内置高级类型
Template Literal Types(模板字面量类型)
type World = 'world';
type Greeting = `Hello ${World}`;
type SupportedLangs = 'en' | 'pt' | 'zh';
type FooterLocaleIDs = 'header' | 'footer';
type AllLocaledIDs = `${SupportedLangs}_${FooterLocaleIDs}_id`;
// en_header_id | en_footer_id
// | pt_header_id | pt_footer_id
// | zh_header_id | zh_footer_id
enum Operate_Type {
ADD ,
DELETE,
UPDATE,
}
type AllLocaledIDs = `${Operate_Type}_${FooterLocaleIDs}_id`;
// 0_header_id | 0_footer_id
// | 1_header_id | 1_footer_id
// | 2_header_id | 2_footer_id
Partial(部分的)
Partial:可以将传入类型中的所有属性全部变成可选的 ?。
源码如下:
// TypeScript/src/lib/es5.d.ts
/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
用法:常见使用场景,对数据进行部分更新时,更新的对象可以使用 Partial<Todo> 来声明。
interface Todo {
title: string;
desc: string;
}
type TodoPatial = Partial<Todo>;
// TodoPatial 等价于 TodoNew 效果
interface TodoNew {
title?: string;
desc?: string;
}
// 常见使用场景,对数据进行部分更新时,更新的对象可以使用 Partial<Todo> 来声明
function updateTodo(todo: Todo, filedsToUpdate: Partial<Todo>) {
return {
...todo,
...fieldsToUpdate,
}
}
const todo1 = {
title: 'hihi',
desc: 'hihi desc'
};
const todo2 = updateTodo(todo1, { desc: 'xxx' });
// 如果是一个带有嵌套结构的类型
type MenuItem = {
label: string;
title: string;
children: MenuItem[];
}
type ParMenuItem = Partial<MenuItem>;
// Error: 缺少 title,children 字段,仅仅对第一层属性做了可选操作
const data: ParMenuItem = {
children: [
{
label: 'sdsd'
}
]
}
EnhancePartial
自定义类型:内置的 Partial 有个局限性,就是只支持处理第一层的属性,如果是嵌套多层的就没有效果了,不过可以如下自定义:
type EnhancePartial<T> = {
// 如果是 object,则递归类型
[U in keyof T]?: T[U] extends object
? EnhancePartial<T[U]>
: T[U]
};
// 指定属性设置为可选的
type PartPartial<T, U extends keyof T> = Omit<T, U> & {
[P in U]?: T[P];
}
举个例子:
// 如果是一个带有嵌套结构的类型
type MenuItem = {
label: string;
title: string;
children: MenuItem[];
}
type ParMenuItem = Partial<MenuItem>;
// ok
const data: ParMenuItem = {
children: [
{
label: 'sdsd'
}
]
}
Required(必须的)
Required:Requiered 和 Partial 作用刚好相反,是将所有属性全部变成必须项。
源码如下:
// TypeScript/src/lib/es5.d.ts
/**
* Make all properties in T required
*/
type Required<T> = {
[P in keyof T]-?: T[P];
};
其中 -? 是代表移除 ? 这个 modifier 的标识。
与之对应的还有个 +? ,这个含义与 -? 正好相反,是把属性变成可选项的,+ 可省略。
用法:
interface Props {
a?: number;
b?: string;
}
const obj: Props = {a: 5};
const obj2: Required<Props> = { a: 5 }; // Error, 缺少属性b
// Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.
一般来说,如果我们提交给接口的时候某些字段是一定需要的,则可以通过 Required 定义一个新类型,保证传入的字段都存在,不存在时,可以用类型检测提示。
Readonly(只读的)
Readonly:将一个类型的所有属性变为只读选项,意味着这个新类型的所有属性都不能被修改。
源码如下:
// TypeScript/src/lib/es5.d.ts
/**
* Make all properties in T readonly
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
给子属性都加上 readonly 标识,如果将上面的 readonly 改成 -readonly, 就是移除子属性的 readonly 标识。
interface Todo {
title: string;
}
const todo: Todo = {
title: '23',
}
todo.title = '2323'; // ok 可以正常修改
const todo1: Readonly<Todo> = {
title: 'ddd',
}
todo1.title = 'hihi'; // Error 报错,title是只读属性
// Cannot assign to 'title' because it is a read-only property.
Record(记录)
Record<Keys, Type>:将 Keys 中的所有属性转为 Type 类型。
/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};
使用:
interface CatInfo {
age: number;
breed: string;
}
type CatName = "miffy" | "boris" | "mordred";
const cats: Record<CatName, CatInfo> = {
miffy: { age: 10, breed: "Persian" },
boris: { age: 5, breed: "Maine Coon" },
mordred: { age: 16, breed: "British Shorthair" },
};
cats.boris; // const cats: Record<CatName, CatInfo>
譬如参数灰度有2 种类型,服务端灰度和客户端灰度,每种灰度对象都是同样类型,那就可以通过 Record 来实现,如:
type GrayType = 'client' | 'server';
type GrayInfo = {
/**
* 灰度流量值
*/
traffic: number;
/**
* 灰度名称,流量灰度,服务灰度
*/
name: string;
}
const grayData: Record<GrayType, GrayInfo> = {
client: {
traffic: 1,
name: '流量灰度',
},
server: {
traffic: 1,
name: '按服务灰度'
}
}
// 若不使用 Record 也可以实现,代码如下
enum GrayType {
client = 'client',
server = 'server',
}
interface GrayInfo {
/**
* 灰度流量值
*/
traffic: number;
/**
* 灰度名称,流量灰度,服务灰度
*/
name: string;
}
interface GrayData {
[GrayType.client]: GrayInfo;
[GrayType.server]: GrayInfo;
}
// 也可以是,这种情况下若需要针对client 或 server 做不同类型操作,就无法获取这两种类型了
interface GrayData {
client: GrayInfo;
server: GrayInfo;
}
// 若后续还要有新的类型出现,譬如 H5 之类的
// 若使用 Record,则只需修改 GrayType,表示增加新的类型
// 若不使用 Record ,需要修改 GrayType 和 GrayData。
Pick(选择)
Pick<Type, Keys>:从某个类型中选取一组属性 keys 来构造一个新的类型,这里的 Keys 可以是字符串字面量或字符串字面量的并集。
源码如下:
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
举个例子
interface Todo {
title: string;
desc: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
// 等价于
type TodoPreview = {
title: string
completed: boolean;
}
Omit(省略)
Omit<Type, Keys>:Omit 和 Pick 作用刚好相反,是从某个类型中移除一组属性。这里的 Keys 就是要被剔除的属性,可以是一个字符串字面量,也可以是由字符串字面量组成的联合类型,如 'a' | 'b'。
源码如下:
/**
* Construct a type with the properties of T except for those in type K.
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
举个例子:
interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: number;
}
type TodoPreview = Omit<Todo, "description">;
// 等价于
type TodoPreview = {
title: string;
completed: boolean;
createdAt: number;
};
type TodoInfo = Omit<Todo, "completed" | "createdAt">;
// 等价于
type TodoInfo = {
title: string,
description: string,
};
Exclude(排除)
Exclude<UnionType, ExcludedMembers>:将某个类型中属于另一个的类型移除掉。
源码如下:
/**
* Exclude from T those types that are assignable to U
*/
type Exclude<T, U> = T extends U ? never : T;
以上的意思是:如果 T 能赋值给 U 类型的话,那么就会返回 never 类型,否则返回 T,最终结果是将 T 中的某些属于 U 的类型移除掉,举个例子:
type T0 = Exclude<'a' | 'b' | 'c', 'a'>;
// 等价于
type T0 = 'b' | 'c';
// 即,将 T = 'a' | 'b' | 'c' 中属于 U='a' 的类型剔除掉,
// 返回的新类型就可以将 U 中的类型给移除掉,也就是 'b' | 'c' 了
type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
// 等价于
type T1 = 'c';
type T2 = Exclude<string | number | (() => void), Function>;
// 等价于
type T2 = string | number;
type Shape = { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
type T3 = Exclude<Shape, { kind: "circle" }>
// 等价于
type T3 = { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
Extract(取出)
Extract<Type, Union>: 提取出 T 包含在 U 中的元素,简单点说就是从 T 中提取出 U。
源码如下:
/**
* Extract from T those types that are assignable to U
*/
type Extract<T, U> = T extends U ? T : never;
举个例子:
type T0 = Extract<'a' | 'b' | 'c', 'a' | 'f'>;
// 等价于
type T0 = 'a';
//
type T1 = Extract<string | number | (() => void), Function>;
// 等价于
type T1 = () => void;
type Shape = { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
// { kind: 'circle', radius: number};
type T2 = Extract<Shape, { kind: 'circle'}>;
// never
type T3 = Extract<Shape, { kind: 'circle', radius: 'hih'}>;
// 等价于 Shape
type T4 = Extract<Shape, { kind: string}>;
NonNullable(不能为null)
NonNullable:过滤类型中的 null 及 undefined 类型。
源码如下:
/**
* Exclude null and undefined from T
*/
type NonNullable<T> = T & {};
举个例子:
// string | number
type T0 = NonNullable<string | number | undefined>;
// string[]
type T1 = NonNullable<string[] | null | undefined>;
// never
type T3 = NonNullable<undefined>;
// never
type T3 = NonNullable<null>;
Parameters(参数)
Parameters:获取函数参数类型组成的元组类型。
源码如下:
/**
* Obtain the parameters of a function type in a tuple
*/
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
举个例子:
// []
type T0 = Parameters<() => string>;
// [s: string]
type T1 = Parameters<(s: string) => void>;
// Error Type '[string, string]' is not assignable to type '[s: string]'
// 类型 T1 是只有一个元素的元组,这里 data 数组有2个元素,类型不匹配
const data: T1 = ['sdfsd', 'sdf'];
// [arg: unknow]
type T2 = Parameters<<T>(arg: T) => T>;
// unknown[]
type T3 = Parameters<any>;
// never
type T4 = Parameters<never>;
// Error: string 不匹配 (...args: any) => any
type T5 = Parameters<string>;
// Error: Function 不匹配 (...args: any) => any
type T5 = Parameters<Function>;
declare function F1(arg: { a: number; b: string }): void;
// [arg: { a: number; b: string }]
type T6 = Parameters<typeof F1>;
ConstructorParameters
ConstructorParameters:获得类的参数类型组成的元组类型。
源码如下:
/**
* Obtain the parameters of a constructor function type in a tuple
*/
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;
上面 abstract new (...args: any) => any 定义的是一个抽象构造函数签名,是 4.2 版本加的新功能,它在语法上有以下三个特点:
- 非抽象构造函数类型可以赋值给抽象构造函数类型。
- 抽象构造函数类型不能赋值给非抽象构造函数类型。
- 包含抽象构造函数类型的值不能通过new实例化(这以前只适用于抽象类)
举个例子:
type Constructor<T> = new (...args: any[]) => T;
type AbstractConstructor<T> = abstract new (...args: any[]) => T;
interface Obj<T> {
x: T;
}
declare let c: Constructor<Obj<number>>;
declare let a: AbstractConstructor<Obj<number>>;
// assignability
a = c; // ok, a non-abstract constructor type can be assigned to an abstract constructor type
c = a; // error, an abstract constructor type cannot be assigned to a non-abstract constructor type
// new expression
new c(); // ok
new a(); // error, cannot create an instance of an abstract class
// inference and conditional types
type T1 = InstanceType<typeof c>["x"]; // number
type T2 = InstanceType<typeof a>["x"]; // number
举个例子:
class Person {
private firstName: string;
private lastName: string;
constructor(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
}
}
type P = ConstructorParameters<typeof Person>; // -> [string, string]
ReturnType(返回类型)
ReturnType:获取函数的返回类型。
源码如下:
/**
* Obtain the return type of a function type
*/
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
实际使用的话,就可以通过 ReturnType 拿到函数的返回类型,如下的示例:
declare function f1(): {a: number; b: string};
// {a: number; b: string}
type T0 = ReturnType<f1>;
// {a: number; b: string}
type T1 = ReturnType<typeof f1>;
// string
type T2 = ReturnType<() => string>;
// number[]
type T3 = ReturnType<<T extends U, U extends number[]>() => T>;
// Error: Type 'string' does not satisfy the constraint '(...args: any) => any'.
// string 并不符合 (...args: any) => any 类型约束
type T4 = ReturnType<string>; // Error
在项目中为了获取某个函数的返回值时,通常这里处理:
let setFalseTimeout: ReturnType<typeof setTimeout>;
// 在 vue 项目中获取 app 对象类型时
export const registryComponents = (app: ReturnType<typeof createApp>)
=> {
...
}
InstanceType(构造函数返回类型、实例类型)
InstanceType:获取构造函数类型的实例类型。
源码如下:
/**
* Obtain the return type of a constructor function type
*/
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;
举个例子:
class C {
x = 0;
y = 0;
}
// T0的类型是 C
type T0 = InstanceType<typeof C>;
// Error: Type 'Function' does not satisfy the constraint 'abstract new (...args: any) => any'
// Type 'Function' provides no match for the signature 'new (...args: any): any'.
type T1 = InstanceType<Function>;
// Error: Type 'string' does not satisfy the constraint 'abstract new (...args: any) => any'.
type T2 = InstanceType<string>;
在项目中最常使用 InstanceType 来获取某个组件类型,常用场景如下:
<template>
<search-list ref="searchRef"></search-list>
</template>
import SearchList from '@/components/search-list.vue';
// 获取 SearchList 类型的实例类型,这样我们通过 ref 调用组件expose的方法或数据时可以有类型提示了
const searchRef = ref<InstanceType<typeof SearchList> | null>(null);
Mutable(可变的)
Mutable:将某个类型所有属性的 readonly 移除。
源码如下:
/** @internal */
export type Mutable<T extends object> = {
-readonly [K in keyof T]: T[K]
};
举个例子:
interface T0 {
readonly name: string;
readonly title: string;
}
type T1 = Mutable<T0>;
// 等价于
type T1 = {
name: string;
title: string;
}
参考资料
基础类型:www.typescriptlang.org/docs/handbo…
keyof: www.typescriptlang.org/docs/handbo…
typeof: www.typescriptlang.org/docs/handbo…
条件类型:www.typescriptlang.org/docs/handbo…
高级类型:www.typescriptlang.org/docs/handbo…
类型保护:jkchao.github.io/typescript-…
TS小抄:www.typescriptlang.org/cheatsheets
交集类型:www.typescriptlang.org/docs/handbo…
www.typescriptlang.org/docs/handbo…
泛型及应用:juejin.cn/post/684490…
Record使用场景:juejin.cn/post/711204…
抽象构造函数签名:www.typescriptlang.org/docs/handbo…