一文搞懂TS中的泛型
一文搞懂TS中的泛型
1. 什么是泛型
为了便于大家更好地理解f泛型,我们先来举个例子,在这个例子中,我们将一步步揭示泛型的作用。首先我们来定义一个通用的 identity
函数,该函数接收一个参数并直接返回它:
function identity (value) {
return value;
}
console.log(identity(24)) // 24
现在,我们将 identity
函数做适当的调整,以支持 TypeScript 的 number 类型的参数:
function identity(value: number):number {
return value;
}
console.log(identity(24)) // 24
这里 identity
的问题是我们将 number
类型分配给参数和返回类型,使该函数仅可用于该原始类型number。但该函数并不是可扩展或通用的。很明显这并不是我们所希望的。所以当我们想要定义一个变量不确定类型的时候我们该怎么做呢,其实有两种方式:
-
使用any
我们失去了定义应该返回哪种类型的能力,并且在这个过程中使编译器失去了类型保护的作用
-
使用泛型
不预先指定具体的类型,而是在使用的时候在指定类型限制
// 其中T代表一个类型变量,在泛型中通常作为第一个变量名称,除此之外还有一些常见的变量名称
// K(Key):表示对象中的键类型;
// V(Value):表示对象中的值类型;
// E(Element):表示元素类型。
function identity <T>(value: T) : T {
return value;
}
console.log(identity<number>(24)) // 24
// 除了为类型变量显式设定值之外,一种更常见的做法是使编译器自动选择这些类型,从而使代码更简洁。我们可以完全省略尖括号,比如:
console.log(identity(24)) // 24
2.泛型的用法
-
函数中使用
使用方式类似于函数传参,传什么数据类型,T就表示什么数据类型,T也可以换成任意字符串
function identity <T> (arg:T):T{ console.log(arg); return arg; } identity<number>(24);// 返回值是number类型的 24 identity<string | boolean>('identity')//返回值是string类型的 identity identity<string | boolean>(false);//返回值是布尔类型的 false
-
接口中使用
// 注意,这里写法是定义的方法哦
interface Person {
<T,Y>(name:T,age:Y):T
}
const fn:Person = function <T, Y>(name: T, age:Y):Y {
console.log(name, age)
return age;
}
fn('ymx',24);//编译器会自动识别传入的参数,将传入的参数的类型认为是泛型指定的类型
- 类中使用
class Animal<T> {
name:T;
constructor(name: T){
this.name = name;
}
action<T>(say:T) {
console.log(say)
}
}
const dog = new Animal('蛋黄');
dog.action('你好,小蛋黄')
3. 泛型约束
有时我们可能希望限制每个类型变量接受的类型数量,这就是泛型约束的作用。下面我们来举几个例子,介绍一下如何使用泛型约束。
-
确保属性存在 : 有时候,我们希望类型变量对应的类型上存在某些属性。这时,除非我们显式地将特定属性定义为类型变量,否则编译器不会知道它们的存在。
function identity<T>(arg: T): T { console.log(arg.length); // Error return arg; }
在这种情况下,编译器将不会知道
T
确实含有length
属性,尤其是在可以将任何类型赋给类型变量T
的情况下(比如number,number实际上是没有length属性的)。我们需要做的就是让类型变量extends
一个含有我们所需属性的接口,比如这样:interface Length { length: number; } function identity<T extends Length>(arg: T): T { console.log(arg.length); // 可以获取length属性 return arg; }
其中
T extends Length
就相当于告诉编辑器,我们支持已经实现Length
接口的任何类型。之后,当我们使用不含有length
属性的对象作为参数调用identity
函数时,TypeScript 会提示相关的错误信息:identity(24); // 类型“number”的参数不能赋给类型“Length”的参数。
-
检查对象上的健是否存在
// 限制输入的属性名包含在 keyof 返回的联合类型中。具体的使用方式如下
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
在以上的 getValue
函数中,我们通过 K extends keyof T
确保参数 key 一定是对象中含有的键,这样就不会发生运行时错误。
下一步我们来看看如何使用这个getValue
函数:
enum AnnotateType {
POINT = "POINT",
SQUARE = "SQUARE",
LATTICE = "LATTICE",
}
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const info = {
name: "ymx",
annotateType: AnnotateType.POINT
}
const point: AnnotateType = getValue(info, 'annotateType'); // OK
const lattice: string = getValue(info, '_LATTICE_'); // Error
4. 泛型工具类型
-
Partial: 的作用就是将某个类型中的属性全部变为可选项
interface Person { name:string; age:number; }; const Person1 = Partial<Person>; interface Person1 { name?:string; age?:number; } // Partial实现原理 type Partial<T> = { [P in keyof T]?: T[P]; };
-
Record: 的作用是将K中所有的属性转换为T类型
interface PersonInfo {
name: string
}
type Option = 'device'|'edge';
const x: Record<Option, PersonInfo> = {
device: { name: "xxx" },
edge: { name: "aaa" },
};
// Record实现原理
type Record<K extends keyof any, T> = {
[P in K]: T;
};
- Pick: 作用是将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型
interface Todo {
title:string,
desc:string,
time:string
}
type TodoPreview = Pick<Todo, 'title'|'time'>;
const todo: TodoPreview ={
title:'吃饭',
time:'明天'
}
// Pick实现原理
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
-
PickWithPartial: 我们想让Pick返回的类型可选,参照Partial得出我们新造出来的新工具泛型
const someStudentBodyInfo:PickWithPartial<Todo, 'title'|'time'> = { time: '后天' } //PickWithPartial实现原理 type PickWithPartial<T, K extends keyof T> = { [P in K]?: T[P]; };
-
Exclude: 的作用是将某个类型中属于另一个类型的属性移除掉
type letter = Exclude<"a" | "b" | "c", "a">; // "b" | "c" const letters:letter ='b'; // Exclude实现原理 type Exclude<T, U> = T extends U ? never : T;
-
Omit : 跟Pick相反,把选出的排除掉
interface User {
id: number;
name: string;
age: number;
sex: 0 | 1;
tel: number;
}
type EditUser = Omit<User, "id">; // 就是在 User 的基础上,去掉 id 属性
// Omit实现原理
type Omit<T, K extends keyof any>{
[P in Exclude<keyof T, K>]: T[P];
}
- Extract: 跟Exclude相反,从从一个联合类型中取出属于另一个联合类型的子集
type name = Exclude<'小明' | '小张', '小张' | '小王'>
// 等同
type name = '小张'
// Extract实现原理
type Extract<T, U> = T extends U ? T : never;
- Readonly: 所有属性只读
let arr: Readonly<number[]> = [1]
arr = [1] // 此时arr=[1]
arr.push(2) // 类型“readonly number[]”上不存在属性“push”。
// Readonly实现原理
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
-
Required: 与Partial相反,把属性全部转化成必填类型
interface PartProps { a?: string b?: string } // 因为Required,所以要求a, b属性必选 const a:Required<PartProps> = { a: 'a', b: 'b' } // Required实现原理 type Required<T> = { [P in keyof T]-?: T[P]; };
-
NonNullable: 排除null/undefined类型
type studentName = NonNullable<string | null | undefined> // string
// NonNullable实现原理
type NonNullable<T> = T extends null | undefined ? never : T;
-
ReturnType: 返回函数的返回结果类型
type T0 = ReturnType<() => string>; // string type T1 = ReturnType<(s: string) => void>; // void type T2 = ReturnType<<T>() => T>; // {} type T3 = ReturnType<<T extends U, U extends number[]>() => T>; // number[] type T4 = ReturnType<any>; // any type T5 = ReturnType<never>; // any type T6 = ReturnType<string>; // Error type T7 = ReturnType<Function>; // Error // ReturnType实现原理 type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;