参考文章:https://juejin.cn/post/6844904184894980104
泛型指是在定义函数或者类时并不知道类型,在被使用时才知道数据的类型。
泛型先起到一个占位的作用,在使用时接收传递过来的类型。
也可以将泛型类比于参数,参数接收的是变量值,泛型接收的是类型。
研究 TypeScript 的关键问题就是讨论两个类型之间的兼容性,类型之间的关系简单分为三种
子类型与子类不同,仅描述两个类型之间的兼容性,且子类型是包括自身的,就像集合子集也包括自己一样。
作用:保证属性是存在
// 比如 Cat 是 Animal 的子类型,那么类型为 Cat 的变量就可以赋给类型为 Animal 的变量了
// Cat 可以保证有 Animal 上的属性,反之不成立
let b : Animal;
let a : Cat;
b = a ; // 正确
a = b ; // 错误
鸭子类型是 TypeScript 类型的最大特性:仅关注对象上的属性和方法,而不关注继承关系
鸭子类型:只关心属性和行为是否一样,并不关心是不是具体对应的类型(走得像鸭子声音像鸭子就是鸭子)
Class Duck{ swim }
Class Dog{ swim,bark }
在 TypeScript 中 Dog 是 Duck 的子类型,因为它满足了 Duck 上的所有方法和属性。
&
|
语法:<类型变量>
T
被称为类型变量(type variable),T
作为参数泛化成具体类型的作用叫做类型参数化。
说明
1.泛型和变量一样,可以指定多个。比如
,中间使用逗号隔开。
2.泛型和变量一样,可以指定默认值。比如< T = string>
。
3.泛型定义的位置,定义的位置一般在标识符的后面。比如function fn
显示设置:像传递函数一样传递类型给泛型变量。(推荐,更方便阅读)
隐式设置:根据参数类型自动推断类型。(更简洁)
// 显示设置
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity<number, string>(68, "Semlinker"));
//隐式设置
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity(68, "Semlinker"));
泛型接口案例
//泛型接口的定义
interface Identities<V, M> {
value: V,
message: M
}
//泛型接口的使用
function identity<T, U> (value: T, message: U): Identities<T, U> {
console.log(value + ": " + typeof (value));
console.log(message + ": " + typeof (message));
let identities: Identities<T, U> = {
value,
message
};
return identities;
}
console.log(identity(68, "Semlinker"));
泛型类案例
class Point<T> {
x: T
y: T
z: T
constructor(x: T, y: T, z: T) {
this.x = x
this.y = y
this.z = z
}
}
const p1 = new Point('1.33.2', '2.22.3', '4.22.1')
const p2 = new Point<string>('1.33.2', '2.22.3', '4.22.1')
const p3: Point<string> = new Point('1.33.2', '2.22.3', '4.22.1')
接口继承的用法
interface T1 {
name: string
}
interface T2 {
sex: number
}
// 多重继承,逗号隔开
// T3 接口继承于T1,T2接口,T3是T1和T2的子接口,T3接口的要求更多。
interface T3 extends T1,T2 {
age: number
}
// 合法
const t3: T3 = {
name: 'xiaoming',
sex: 1,
age: 18
}
// 普通用法
type A1 = 'x' extends 'x' ? string : number; // string
type A2 = 'x' | 'y' extends 'x' ? string : number; // number
// 泛型用法
type P<T> = T extends 'x' ? string : number;
type A3 = P<'x' | 'y'> // ?
感觉上A2和A3是一样的,但其实A3的类型是 string | number
。
原因:如果extends前面的参数是一个泛型类型,当传入该参数的是联合类型,则使用分配律计算最终的结果。也就是将联合类型分别拆开带入泛型,最后将结果再联合起来。
特殊的never
never
被认为是没有联合项的联合类型,所以还是满足分配律,然而因为没有联合项可以分配,所以P
的表达式根本没有执行,所以A2的定义也就类似于永远没有返回的函数一样,是never类型的。
// never是所有类型的子类型
type A1 = never extends 'x' ? string : number; // string
type P<T> = T extends 'x' ? string : number;
type A2 = P<never> // never
阻止条件判断中的分配律
如果我们希望将联合类型看成一个整体,不适用分配律,那么在条件判断类型的定义中,将泛型参数使用[]
括起来。
下面的例子,传入参数T的类型将被当作一个整体。
type P<T> = [T] extends ['x'] ? string : number;
type A1 = P<'x' | 'y'> // number
type A2 = P<never> // string
extends判断条件的逻辑:如果extends
前面的类型可以直接赋值给extends
后面的类,那么表达式为真
下面的示例中,Dog
是Animal
的子类型,父类型比子类型的限制更少。能满足子类型,则一定能满足父类型。
Dog类型的值可以赋值给Animal类型的值,判断为真。
可以简单理解为,满足前面类型的数据是否能满足后面的类型。
// 示例1
interface Animal {
eat(): void
}
interface Dog extends Animal {
bite(): void
}
// 如果问号前面的判断为真,则将第一个类型string赋值给A,否则将第二个类型number赋值给A。
type A = Dog extends Animal ? string : number
const a: A = 'this is string'
Exclude
语法定义:type Exclude
作用:其作用是从第一个联合类型参数中,将第二个联合类型中出现的联合项全部排除,只留下没有出现过的参数。
示例
type A = Exclude<'key1' | 'key2', 'key2'> // 'key1'
type Exclude<T, U> = T extends U ? never : T
//T为'key1' | 'key2',U为'key2' extends前面的类型是一个泛型,并且是一个联合类型,分配律奏效
type A = ('key1' extends 'key2' ? never : 'key1') | ('key2' extends 'key2' ? never : 'key2')
// =>
type A = 'key1' | never
//never是所有类型的子类型
type A = 'key1'
Extract
原理:type Extract
作用:将第二个参数的联合项从第一个参数的联合项中提取出来。(第二个参数可以含有第一个参数没有的项)
type A = Extract<'key1' | 'key2', 'key2'> // 'key2'
type Extract<T, U> = T extends U ? T : never
// T为'key1' | 'key2',U为'key2' extends前面的类型是一个泛型,并且是一个联合类型,分配律奏效
type A = ('key1' extends 'key2' ? 'key1' : never) | ('key2' extends 'key2' ? 'key2' : never)
// =>
type A = never | 'key2'
//never是所有类型的子类型
type A = 'key2'
作用:在类型未推导时进行占位,等到真正推导成功后,能准确地返回正确类型。
比如:条件语句 T extends (...args: infer P) => any ? P : T
中,infer P
表示待推断的函数参数。
infer
只能在 extends
条件语句中使用,声明变量只能在true分支
中使用
// numberPromise 是否可以赋值给 Promise P占位,如果可以则n = P推导出来的类型
type numberPromise = Promise<number>;
type n = numberPromise extends Promise<infer P> ? P : never; // number
keyof 类型
接受一个对象类型作为参数,返回该对象属性名组成的字面量联合类型。通过 keyof 操作符,可以获取指定类型的所有键in
的右侧一般会跟一个联合类型,使用in
操作符可以对该联合类型进行迭代。 其作用类似JS中的for...in
或者for...of
//keyof使用案例
type Dog = { name: string; age: number; };
type D = keyof Dog; //type D = "name" | "age"
//in的使用案例
type Animals = 'pig' | 'cat' | 'dog'
type animals = {
[key in Animals]: string
}
// type animals = {
// pig: string; //第一次迭代
// cat: string; //第二次迭代
// dog: string; //第三次迭代
// }
keyof any
返回联合类型string | number | symbol
补充:遇见索引签名时,keyof
会直接返回索引的类型。索引类型为string
时,keyof
返回的类型是string | number
,这是因为数字类型索引最终访问时也会被转换为字符串索引类型。
type Dog = { [y:number]: number };
type dog = keyof Dog; //type dog = number
type Doggy = { [y:string]: boolean };
type doggy = keyof Doggy; //type doggy = string | number
使用场景:与extends
关键字结合使用,对对象的属性做限定。
对泛型的类型进行约束
使用场景:希望变量的类型上存在某属性
interface Length {
length: number; //这个是特殊用法,表示该变量只要有length就可以,有多余的属性也可以
}
//T extends Length 用于告诉编译器,支持已经实现 Length 接口的任何类型。 接口的继承用法
function identity<T extends Length>(arg: T): T {
console.log(arg.length); // 可以获取length属性
return arg;
}
extends
与 keyof
结合限定类型建 ,表示extends
前面的类型是keyof
返回的联合类型中的一个。
extends
不一定要强制满足继承关系,检查是否满足结构兼容性。
// k extends "name"| "age" 这里确保参数 key 一定是对象中含有的键
function getObjectProperty<O,K extends keyof O>(obj:O,key:K){
return obj[key]
}
const info = {
name:"ranran",
age:18
}
getObjectProperty(info,"name")
映射类型:可以理解为将一个类型映射成一个新的类型
使用场景:一个类型需要另一个类型,但是又不想拷贝(映射)一份,可以考虑使用映射类型。
可以将映射类型想象成一个函数,函数的作用就是拷贝(映射)类型
//映射类型MapPerson
//<>定义泛型,接收需要拷贝的类型
type MapPerson<Type> = {
//[index:number]:any //索引签名写法
//Property自定义的标识符, keyof Type表示联合类型,in从联合类型中依次取值赋值给Property
[Property in keyof Type]:Type[Property]
}
interface Iperson{
name:string
age:number
}
type NewPerson = MapPerson<Iperson> //使用映射类型拷贝Iperson类型
readonliy
设置属性只读?
设置属性可选可以通过前缀-
或者+
删除或添加这些修饰符,默认使用+
//映射类型MapPerson
//将所有属性映射为可选属性
type MapPerson<Type> = {
+readonly [Property in keyof Type]:Type[Property]
}
interface Iperson{
name:string
age:number
}
type NewPerson = MapPerson<Iperson>
/*
type NewPerson = {
readonly name: string;
readonly age: number;
}
*/
Partial
的作用就是将某个类型里的属性全部变为可选项 ?
Partial
的实现可以看成映射类型 + 可选?修饰符
// P为key T为类型对象
//通过 keyof T 拿到 T 的所有属性名,然后使用 in 进行遍历,将值赋给 P,最后通过 T[P] 取得相应的属性值。中间的 ? 号,用于将所有属性变为可选。
type Partial<T> = {
[P in keyof T]?: T[P];
};
Record
将 K
中所有属性的值转化为 T
类型
keyof any
返回联合类型string | number | symbol
,K extends keyof any
表示的是泛型参数 K 必须是一个能作为对象键的类型。定义
//[P in K] 表示的是:遍历 K 中的每个键,并将其作为属性名,然后将类型 T 分配给每个属性。
type Record<K extends keyof any, T> = {
[P in K]: T;
};
示例
interface PageInfo {
title: string;
}
type Page = "home" | "about" | "contact";
/*
Record返回
{
about: { title: string},
contact: { title: string},
home: { title: string}
}
*/
const x: Record<Page, PageInfo> = {
about: { title: '任意字符串'},
contact: { title: '任意字符串'},
home: { title: '任意字符串'}
};
使用场景
创建一个特定key并且值类型一样的对象
interface IPerson {
name: string;
age: number;
email: string;
gender: string;
};
type Person = Record<keyof IPerson, string>
const person: Person = {
name: "Echo",
age: "26",
email: "[email protected]",
gender: "Male",
};
注意点
1.属性名唯一,如果重复了,后面的键值会覆盖前面的
2.Record
3.Record
4.所有属性的属性值都应该具有相同的类型 T,否则 TypeScript 编译器会报错。
Pick
将某个类型中的子属性挑出来
定义
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};