泛型
泛型作用非常广泛 ,它有着很多的应用场景
- 泛型接口
- 泛型类
- 泛型约束
- 泛型的默认参数
- 泛型的条件类型
- 泛型工具类
- 使用泛型创建对象
1. 什么是泛型
- 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
// 泛型可定义多个 在这定义两个泛型T,U
function identity<T, U>(value: T, message: U): T {
console.log(message);
return value;
}
console.log(identity < Number, string > (68, "Semlinker"));
// console.log(identity(68, "Semlinker")); 简写 类型可根据参数类型自动检测 不用传递类型
我们在声明identity
函数名后<>
里声明两个泛型T,U
。在不调用该函数时我们不知道T,U
的类型。只有我们在调用 identity <Number,string> (68, "Semlinker")
时,在<>中传递Number,string
时才知道 T的类型时Number, U的类型时string
。使用泛型时你就把它看成参数传递!!
当然在你调用函数时也可以不向泛型传递类型,ts 会自动帮你检测你的参数类型!!
2. 泛型接口
- 函数使用时可以省略不传递类型,但接口得传
interface Identities<V, M> {
value: V;
message: M;
}
let a: Identities = {}; // 报错 缺少参数
let a: Identities<number, string> = {
value:2
message:"7777"
};
// 接口泛型接口可以作用 很多地方 在这例子比较简单 但理解都是一样的
3. 泛型类
- 在类中使用泛型 和函数中也是一样的
class A<T> {
// T 接受类型是string
value: T; // 限制value 的类型string
constructor(value: T) {
// 限制了类A接受的参数得是一个string类型
this.value = value;
}
getIdentity(): T {
// 限制返回值是string
return this.value;
}
}
let newA = new A("777"); // ts自动检测 泛型 参数是string 省略<string>不写
// let newA = new A<string>("777");
4. 泛型约束
- 有时候,我们希望 参数 上存在某些属性
// arg类型是T ,T的类型是根据identity传递参数时的类型,
function identity<T>(arg: T): T {
console.log(arg.length); // Error 但这个参数可能是一个数字 数字是不存在length属性
return arg;
}
使用接口来进行 泛型约束 来限制使用该函数时传递的参数必须有 length 属性
interface Length {
length: number;
}
// extends 继承 在这可理解为T继承了 Length 接口的类型限制 ,多了一种条件
function identity<T extends Length>(arg: T): T {
console.log(arg.length); // 可以获取length属性
return arg;
}
// 在我们调用该函数时传入的参数就必须要含有length属性 不然会报错提示
4.2 keyof 操作符
- keyof 操作符返回某个类型的所有键 ,是一个联合类型
interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
通过 keyof 操作符,我们就可以获取指定类型的所有键。结合前面介绍的 extends 约束,即限制输入的属性名包含在 keyof 返回的联合类型中。
let tsInfo = {
name: "Typescript",
age: 7,
}
// k 继承了 keyof T 的类型, 而 keyof T 返回联合类型 name | age 存放在 T 里。
// 这就限制了我们第二个参数只能是tsInfo的键名
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
getProperty(tsInfo, 'name'); // OK Typescript
getProperty(tsInfo, 'n_ame'); // Error
5、泛型参数默认类型
- 泛型的参数的默认值就和函数的参数是一样的。 当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推断出类型时,这个默认类型就会起作用。
interface A<T = string> {
name: T;
}
const strA: A = { name: "Semlinker" }; // 没有指定类型 那么就是默认类型string
const numB: A<number> = { name: 101 }; // 指定了类型number
6. 类型兼容
- 类型兼容性用于确定一个类型是否能赋值给其他类型,TypeScript 结构化类型系统的基本规则是,如果 x 要兼容 y,那么 y 至少具有与 x 相同的属性。
interface INamed {
name: string;
}
let x: INamed = { name: "77" };
let y = {
name: "funlee",
age: 18,
};
x = y; // ok
y = x; // error x想要赋值给y 就要包含y中所有的值
7. 泛型条件类型
- 条件类型,使得我们可以根据某些条件得到不同的类型,
T extends U ? X : Y
结合类型兼容的知识来理解 ,若 T 能赋值给 U,则类型为 X,否则类型为 Y 有点类似于 JavaScript 中的三元条件运算符。在这里 extends 你理解为 包含 会更明白点。
interface Water {
water: string
}
interface Fish {
water: string
fish: string,
}
interface Sky {
sky: string
}
// 当 T 接受传递的类型 它会看看该类型是否包含着 Fish , true 返回 warter, false返回Sky
type Condition<T> = T extends Fish ? Water : Sky;
let condition1: Condition<Water> = { water: '水' };
// error T 赋值不了 Fish 缺少 fish 字段 ,所以此时的类型是 Sky
let condition2: Condition<Fish> = { water: '水' };
// ok T 能够赋值 Fish
6.2 infer 关键字
- infer 的作用一言蔽之:抽取泛型参数
// infer R 表示声明一个变量 R 并将抽取(推断)的类型保存在里面
type Type<T> = T extends Array<infer R> ? R : T;
type TypeA = Type<string[]> // string
type TrypB = Type<never> // nerver
我们来分析一下以上代码:
- 首先判断泛型 T 是否数组类型;
- 如果是数组类型,将数组成员类型 R 抽出来并返回;
- 如果不是数组类型,则原样返回 T;
案例可能比较简单但是就是这个理,大家可以在网上寻找更多例子
7. 泛型工具
7.1 Partial
- 将传入的属性变为可选项
interface IPeople {
title: string;
name: string;
}
const people: IPeople = {
title: 'Delete inactive users'
};
// error 缺少了name属性
const people: Partial<IPeople> = {
title: 'Delete inactive users'
};
// ok 通过 Partial 将接口 Ipeople 的属性变为可选类型
7.2 Record<K, T>
- 类型参数 K 提供了对象属性名联合类型,类型参数T提供了对象属性的类型
interface Person {
name: string;
}
// 传递 x y,此时的 x y 的类型都是Person
type Peoples = Record<"x" | "y", Person>; // 等于下面写法
// Peoples={
// x:Person,
// Y:Person,
// }
const P: Peoples = {
x: {
name: '张三'
},
y: {
name: '李四'
}
}
7.3 Readonly
- 把传入的类型变为只读状态
interface Person {
name: string;
age: number;
}
// 设置值后就不可以再次更改了
const p: Readonly<Person> = {
name: '张三',
age: 22
}
p.name = '李四'; // 无法分配到 "name" ,因为它是只读属性
7.4 Required
- 把传入的类型变为必填状态
interface Person {
name?: string;
age?: number;
}
const p: Required<Person> = {
name: '张三',
age: 22
}
7.5 Exclude<T, U>
- 该工具类型能够从类型T中剔除所有可以赋值给类型U的类型
type T0 = Exclude<"a" | "b" | "c", "a">;
// 相当于 type T0 = "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
// 相当于 type T1 = "c"
type T2 = Exclude<string | number | (() => void), Function>;
// 相当于 type T2 = string | number
7.6 Extract<T, U>
- 表示 如 T 是、能够赋值给 U 返回 never 类型,如不是返回 T 类型。 当 T 为联合类型的时候,它会自动分发条件。
type T0 = Extract<'a' | 'b' | 'c', 'a' | 'f'>;
// 相当于 type T0 = 'a';
type T1 = Extract<string | (() => void), Function>;
// 相当于 type T1 = () => void;
type T2 = Extract<string | number, boolean>;
// 因为没有交集,相当于 type T2 = never;
联合类型的话 它会一个个去比较 若能赋值他就会保留 该类型,它和 Exclude 类似。
Exclude 去除相同的
Extract 保留相同的
7.7 Omit<T, K>
- 在 T 中删除对应的 K
interface IPerson {
name: string;
age: number;
}
type TP = Omit<IPerson, 'age'>;
const p: TP = {
name: '张三',
age:3,// 报错 没有该类型
}
7.8 ReturnType
- 该工具类型能够获取函数类型T的返回值类型
// string
type T0 = ReturnType<() => string>;
// { a: string; b: number }
type T1 = ReturnType<() => { a: string; b: number}>;
文章借鉴
知乎一文读懂泛型
掘金泛型工具及实现原理