TypeScript泛型
软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性
不用泛型的话,这个函数可能是下面这样:
function getValue(arg: number): number {
return arg;
}
2
3
现在需求有变,需要返回一个 number 类型的值,你会说,联合类型就完事了:
function getValue(arg:string | number):string | number {
return arg;
}
2
3
或者,我们使用any
类型来定义函数:
function getValue(arg: any): any {
return arg;
}
2
3
尽管 any 大法好,很多时候 any 也确实能够解决不少问题,但是这样也不符合我们的需求了,传入和返回都是 any 类型,传入和返回并没有统一
# 基本使用(一个参数)
因此,我们需要一种方法使返回值的类型与传入参数的类型是相同的
我们使用了类型变量,它是一种特殊的变量,只用于表示类型而不是值
泛型的语法是尖括号 <>
里面写类型参数,一般用 T
来表示第一个类型变量名称:
function getValue<T>(arg:T):T {
return arg;
}
2
3
泛型就像一个占位符一个变量,在使用的时候我们可以将定义好的类型像参数一样传入,原封不动的输出
# 两种方式使用:
定义要使用的类型
getValue<string>('宝贝'); // 定义 T 为 string 类型
1利用 typescript 的类型推断
getValue('宝贝') // 自动推导类型为 string
1
# 多个参数
其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U
function getValue<T, U>(arg:[T,U]):[T,U] {
return arg;
}
// 使用
const str = getValue(['树哥', 18]);
2
3
4
5
6
# 泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法:
function getLength<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
2
3
4
因为泛型 T 不一定包含属性 length,那么我想 getLength 这个函数只允许传入包含 length 属性的变量,该怎么做呢
interface Lengthwise {
length: number;
}
function getLength<T extends Lengthwise>(arg:T):T {
console.log(arg.length);
return arg;
}
2
3
4
5
6
7
8
使用:
const str = getLength('宝贝')
const arr = getLength([1,2,3])
const obj = getLength({ length: 5 })
2
3
# 泛型接口
在定义接口的时候指定泛型
interface KeyValue<T,U> {
key: T;
value: U;
}
const person1:KeyValue<string,number> = {
key: '树哥',
value: 18
}
const person2:KeyValue<number,string> = {
key: 20,
value: '张麻子'
}
2
3
4
5
6
7
8
9
10
11
12
13
# 泛型类
class Test<T> {
value: T;
add: (x: T, y: T) => T;
}
let myTest = new Test<number>();
myTest.value = 0;
myTest.add = function (x, y) {
return x + y;
};
2
3
4
5
6
7
8
9
10
# 泛型类型别名
type Cart<T> = { list: T[] } | T[];
let c1: Cart<string> = { list: ["1"] };
let c2: Cart<number> = [1];
2
3
# 泛型参数的默认类型
我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。有点 js 里函数默认参数的意思。
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
2
3
4
5
6
7
# 泛型工具类型
typeof
除了做类型保护,还可以从实现推出类型
//先定义变量,再定义类型 let p1 = { name: "树哥", age: 18, gender: "male", }; type People = typeof p1; function getName(p: People): string { return p.name; } getName(p1);
1
2
3
4
5
6
7
8
9
10
11keyof
用来获取一个对象接口中的所有 key 值
interface Person { name: string; age: number; gender: "male" | "female"; } type PersonKey = keyof Person; //type PersonKey = 'name'|'age'|'gender'; function getValueByKey(p: Person, key: PersonKey) { return p[key]; } let val = getValueByKey({ name: "树哥", age: 18, gender: "male" }, "name"); console.log(val); // 树哥
1
2
3
4
5
6
7
8
9
10
11
12
13in
用来遍历枚举类型
type Keys = "a" | "b" | "c" type Obj = { [p in Keys]: any } // -> { a: any, b: any, c: any }
1
2
3
4
5infer
在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用
infer R 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用
type ReturnType<T> = T extends ( ...args: any[] ) => infer R ? R : any;
1
2
3extends
有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束
interface Lengthwise { length: number; } function loggingIdentity<T extends Lengthwise>(arg: T): T { console.log(arg.length); return arg; }
1
2
3
4
5
6
7
8现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:
loggingIdentity(3); // Error, number doesn't have a .length property loggingIdentity({length: 10, name: '张麻子'}); // 编译正确
1
2索引访问操作符
使用
[]
操作符可以进行索引访问:interface Person { name: string; age: number; } type x = Person["name"]; // x is string
1
2
3
4
5
6
# 内置工具类型
Required
将类型的属性变成必选
interface Person { name?: string, age?: number, hobby?: string[] } const user: Required<Person> = { name: "宝贝", age: 18, hobby: ["code"] }
1
2
3
4
5
6
7
8
9
10
11Partial
与 Required 相反,将所有属性转换为可选属性
interface Person { name: string, age: number, } const user:Person = { name:'宝贝' } // error Property 'age' is missing in type '{ name: string; }' but required in type 'Person'. // 从上面知道,如果必传而我们少传了的话,就会报错
1
2
3
4
5
6
7
8
9
10使用 Partial 将其变为可选
type User = Partial<Person> const user: User={ name:'树哥' } // 编译正确
1
2
3
4
5Exclude
Exclude<T, U>
的作用是将某个类型中属于另一个的类型移除掉,剩余的属性构成新的类型type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c" type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c" type T2 = Exclude<string | number | (() => void), Function>; // string | number
1
2
3Extract
和 Exclude 相反,
Extract<T,U>
从 T 中提取出 Utype T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a" type T1 = Extract<string | number | (() => void), Function>; // () =>void
1
2Readonly
把数组或对象的所有属性值转换为只读的,这就意味着这些属性不能被重新赋值
interface Person { name: string; age: number; gender?: "male" | "female"; } let p: Readonly<Person> = { name: "hello", age: 10, gender: "male", }; p.age = 11; // error Cannot assign to 'age' because it is a read-only property.
1
2
3
4
5
6
7
8
9
10
11
12Record
Record<K extends keyof any, T>
的作用是将 K 中所有的属性的值转化为 T 类型type Property = 'key1'|'key2' type Person = Record<Property, string>; const p: Person = { key1: "hello", key2: "宝贝", };
1
2
3
4
5
6
7Pick
从某个类型中挑出一些属性出来
type Person = { name: string; age:number; gender:string } type P1 = Pick<Person, "name" | "age">; // { name: string; age: number; } const user:P1={ name:'宝贝', age:18 }
1
2
3
4
5
6
7
8
9
10
11
12Omit
与Pick相反,
Omit<T,K>
从T中取出除去K的其他所有属性interface Person { name: string, age: number, gender: string } type P1 = Omit<Person, "age" | "gender"> const user:P1 = { name: '宝贝' }
1
2
3
4
5
6
7
8
9NonNullable
去除类型中的
null
和undefined
type P1 = NonNullable<string | number | undefined>; // string | number type P2 = NonNullable<string[] | null | undefined>; // string[]
1
2ReturnType
用来得到一个函数的返回值类型
type Func = (value: string) => string; const test: ReturnType<Func> = "1";
1
2Parameters
用于获得函数的参数类型所组成的元组类型
type P1 = Parameters<(a: number, b: string) => void>; // [number, string]
1InstanceType
返回构造函数类型T的实例类型
class C { x = 0; y = 0; } type D = InstanceType<typeof C>; // C
1
2
3
4
5
6