1. 高级类型的价值
TypeScript提供了较多的高级类型,通过学习高级类型可以帮助提高TS代码的灵活运用能力,掌握好这些高级类型能进一步提升我们对泛型的理解和驾驭能力,让TS功底更深厚,把我们的TS水平推向一个更高的层次,无论以后在项目中运用TS还是对理解源码的复杂TS泛型语法都有不小的帮助。
由于TS高级类型为我们提供了很多技巧性强的功能, 当我们在项目中遇到使用这些功能的应用场景时,会给项目带来更简洁,更轻量级的实现效果,比如:如果我们项目中只需要查询 key value 数据,那么Record类型就是轻量级的Map,再比如Omit快捷爬取Todo列表中的数据,保证编辑和预览时的不同效果。
2. infer
:::info infer的定义:infer表示extends条件语句中以占位符出现的用来修饰数据类型的关键字,被修饰的数据类型等到使用时才能被推断出来。 ::: infer占位符式的关键字出现的位置:
- extends条件语句后的函数类型的参数类型位置上
- extends条件语句后的函数类型的返回值类型上
- 泛型的具体化类型上
```typescript
// 1.infer出现在extends条件语句后的函数类型的参数类型上
type inferType
= T extends (params: infer P) => any ? P : T; // 2.infer出现在extends条件语句后的函数类型的返回值类型上 type inferType2 = T extends (params: any) => infer P ? P : T;
// 3.出现在泛型的具体化类型上
// 例如,获取Set元素的具体类型
type ElementOf0
vue3中的infer```typescriptfunction unref<T>(ref: T): T extends Ref<infer V> ? V : T {return isRef(ref) ? (ref.value as any) : ref;}
3. infer构建带参数的工厂函数
使用infer获得构造函数的参数类型,然后利用该参数类型进一步限制工厂函数的参数类型,代码更加严谨。
type ConstructorParamsType<T extends new (...args: any[]) => any> =T extends new (...args: infer P) => any ? P : never;type Constructor<T> = new (...args: any[]) => T;function createInstance<T, P extends new (...args: any[]) => any>(constructor: Constructor<T>,...args: ConstructorParamsType<P>) {return new constructor(...args);}let instance = createInstance<TestClass, typeof TestClass>(TestClass,"xiaoming",23);console.log(instance.name);
4. Extract
:::info
Extracttype Extract<T, U> = T extends U ? T : never
:::
extends有以下特点
- 在父子类中:
子类 extends 父类返回true;父类 extends 子类一般为false,除非父类和子类的实例属性和方法(无论是public、private还是protected)都和父类完全一致。 - 在联合类型中:TS会将T分解和U比较并合成得出结果。如
Extract<string | number, string>,第一步:string extends string ? T : never得到string;第二步number extends string ? T : never得到never,将string和never合并,得出最后的结果never。 - 在函数中:函数类型上的泛型约束,在参数类型和返回值完全相同的情况下,参数少的函数类型extends参数多的函数类型,返回true;参数多的函数类型extends参数少的函数类型,返回false。 ```typescript class People { public name!: string; public age!: number; public address!: string; eat() {} }
class ChinesePeople extends People { private phone!: string; }
type Extract
// 1.父子类中
type extractType = Extract
// 2.联合类型中
type unionExtractType = Extract
// 3.在函数中 type func1 = (one: number, two: string) => string type func2 = (one: number) => string
// 函数的泛型约束 type beginType1 = func1 extends func2 ? func1 : never // never type beginType2 = func2 extends func1 ? func2 : never // func2
type extractType1 = Extract
【应用】可以简化<T extends object>这样的泛型,例如:```typescriptfunction cross<T, U>(obj1: Extract<T, object>, obj2: Extract<U, object>): T & U;function cross<T, U, V>(obj1: Extract<T, object>,obj2: Extract<U, object>,obj3: Extract<V, object>): T & U & V;function cross<T, U, V>(obj1: Extract<T, object>,obj2: Extract<U, object>,obj3?: Extract<V, object>): any {...}
5. ⭐Exclude
:::info
Excludetype Exclude<T, U> = T extends U ? never : T
:::
interface MyWorker {name: string;age: number;email: string;salary: number;}interface Student {name: string;age: number;email: string;grade: number;}// 获取MyWorker中的"age" | "email" | "salary",使用Exclude更方便type resultType = Extract<"age" | "email" | "salary" | "xx", keyof MyWorker>; //"age" | "email" | "salary"type resultType2 = Exclude<"name" | "xx", keyof MyWorker>; // xxtype resultType3 = Exclude<keyof MyWorker, "name">; // "age" | "email" | "salary"// 获取MyWorker中有,但学生中没有的属性type resultType4 = Exclude<keyof MyWorker, keyof Student>; // "salary"// 获取MyWorker和Student中共同的属性type resultType5 = Extract<keyof MyWorker, keyof Student>;
:::warning
【区别】Extract排除条件不成立的类型,保留符合泛型约束条件的类型Exclude排除符合泛型约束条件的,保留条件不成立的类型
:::
6. Record
K extends keyof T的使用
type Worker = {custname: string;};type Customer = {custname: string;age: number;};type oneType<T, K> = K extends keyof T ? K : never;type oneTypeResult = oneType<Customer, "custname">; // 输出custname类型type twoType<T, K> = K extends keyof T ? T[K] : never;type twoTypeResult = twoType<Customer, "age">; // 输出number
:::info
Recordtype Record<K extends keyof any, T> = {[P in K]: T}
其中type keyof any = string | number | symbol
:::
const goodSymid = Symbol("goodid");interface Goods {[goodSymid]: number;name: string;price: number;}const goodsList: Goods[] = [{[goodSymid]: 101,name: "苹果",price: 9,},{[goodSymid]: 102,name: "香蕉",price: 3,},{[goodSymid]: 103,name: "橘子",price: 3,},];// 实现方式let goodRecord: Record<number, Goods> = {};goodsList.forEach((good) => {goodRecord[good[goodSymid]] = good;});console.log("goodRecord:", goodRecord);
:::warning Record和Object、Map的区别 :::
// 定义Goods接口const goodSymid = Symbol("goodid")interface Goods {[goodSymid]: numbername: stringprice: number}type Record<T> = {[P in keyof any]: T}// Record和object区别// 区别1:Record 获取到是索引参数类型,所以可以赋初值为{}// 而object也可以,但是再次赋值,比如: goodRecord[103] = good2;// 会出现错误,会查找103属性是否存在于object类型的对象变量中// 区别2: Record是泛型,获取值可以有自动提示功能,而object无法实现自动提示。type resultGoodsType = Record<Goods>let good: Goods = { [goodSymid]: 101, "name": "苹果", "price": 9 }// let obj={} 这样一种情况// let goodRecord = { name: "wangwu", 103: good }// let good2: Goods = { [goodSymid]: 101, "name": "苹果", "price": 9 }// goodRecord[103] = good2;// 修改103的值为good2,而不能增加103属性名[key],js可以增加// goodRecord["香蕉"] = good// goodRecord[good[goodSymid]] = goodlet goodRecord: Object = { name: "wangwu", 103: good }let good2: Goods = { [goodSymid]: 101, "name": "苹果", "price": 9 }// 编译器尝试从object上查找103属性,// 但object什么属性和方法都没有,只是单纯表示一个对象的类型// Object也不行,因为也没有103属性,直接抛出 类型“Object”上不存在属性“103”。ts(7053)// goodRecord[103] = good2;// 修改103的值为good2,而不能增加103属性名【key],js可以增加// goodRecord["香蕉"] = good// goodRecord[good[goodSymid]] = good// console.log("goodRecord:", goodRecord);// // Record类型对于取出来的对象,可以自动提示输出对象的属性和方法// for (let goodid in goodRecord) {// let good = goodRecord[goodid];// console.log(goodid, ":", good)// }
// Record和object,Map区别// 定义Goods接口const goodSymid = Symbol("goodid")interface Goods {[goodSymid]: numbername: stringprice: number}type Record<T> = { [P in keyof any]: T }// 实际开发为什么我们在显示数据,数据扁平化时用Record// 原因1:是因为Record有多种实现方式,比如S100实现方式,Map就需要改底层源码才能做到【一般是不会改的】// type Record<T> = {[P in keyof any]: T}// 原因2:Record是属于一个轻量级的type类型,Map相对Record是重量级// 而且Map需要new出来的,所以要更加占用内存空间// 如果读取数据和显示数据频繁,就应该采用Record// 如果增删改比较多,那还是使用Maptype resultGoodsType = Record<Goods>let goodMap = new Map<any, Goods>();let good: Goods = { [goodSymid]: 101, "name": "苹果", "price": 9 }goodMap.set(103, good)goodMap.set("香蕉", good)goodMap.set(good[goodSymid], good);// Record类型对于取出来的对象,可以自动提示输出对象的属性和方法// for (let goodid in goodRecord) {// let good = goodRecord[goodid];// console.log(goodid, ":", good)// }
7. Pick
:::info
Pick主要用于提取某种数据类型的属性,但实际工作中,主要用来提取接口或type定义的对象类型中的属性。type Pick <T, K extends keyof T> = { [P in K] : T[P] };
:::
基本使用
interface Book {ISBN: string;book_name: string;book_price: string;book_store_count: number;book_publish: string;}type myBook = Pick<Book, "book_name" | "ISBN" | "book_price">;//结果为:type myBook = {book_name: string;ISBN: string;book_price: string;};
interface Todo {title: string;completed: boolean;description: string;}const todonew: Pick<Todo, "title"> = {title: "下午3点美乐公园参加party",};const todonew2: Pick<Todo, "title" | "completed"> = {title: "下午3点美乐公园参加party",completed: false,};
8. ⭐Omit
Omit是为了反向抓取数据
type Omit<T, K extends string | number | symbol>= { [P in Exclude<keyof T, K>]: T[P]; }
也类似于以下type,不同之处为Omit对K是否继承keyof T没有要求。
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;interface Todo {title: string;completed: boolean;description: string;}type TodoPreview = Omit<Todo, "description">;type TodoPreview = {title: string;completed: boolean;};
9. Required & Partial & ReadOnly
:::info
Partial<T>一次性全部变成可选选项的高级类型Required<T>一次性全部变成必选选项的高级类型ReadOnly<T>一次性全部变成只读选项的高级类型
:::
type Partial<T> = {[P in keyof T]?: T[P]}type Required<T> = {[P in keyof T]-?: T[P]}type ReadOnly<T> = {readonly [P in keyof T]: T[P]}
10. 复杂面试题
interface Action<T = any> {type: string;payload?: T;}class FoodModule {public static topic: string;public count!: number;delay(promise: Promise<number>) {return promise.then((second: number) => ({type: "delay",payload: `延迟${second}秒`,}));}searchFoodByCity(action: Action<string>) {return {payload: action.payload,type: "searchFoodByCity",};}}【实现1】FoodModule参数类型和返回值类型被部分过滤,如下:// 过滤后的delay方法类型type asyncMethodConnect<T, U> = (input: T) => Action<U>// 过滤后的searchFoodByCity方法类型type asyncMethodConnect<T, U> = (action: T) => Action<U>【实现2】就是把这两个类型和原来的FoodModule类合并成如下对象type Convert = () => {delay: asyncMethodConnect<number, string>;searchFoodByCity: asyncMethodConnect<string, string>;}
11. Promise源码片段
主要关注Promise构造器的类型
type ResolveType = (resolve: any) => any;type RejectType = (reject: any) => any;class MyPromise {public resovleFunc!: ResolveType;public rejectFunc!: RejectType;constructor(promiseParams: (resolve: ResolveType, reject: RejectType) => any) {this.resovleFunc = (resolve: any): any => {console.log(resolve);};this.rejectFunc = (reject: any): any => {console.log(reject);};promiseParams(this.resovleFunc, this.rejectFunc);}}new MyPromise(function (resovle: ResolveType, reject: RejectType): any {resovle("success");});
12. Vuex源码片段
interface StoreOptions<S> {state: S;actions: ActionTree<S, S>;}interface ActionTree<S, R> {[key: string]: ActionHandler<S, R>;}type ActionHandler<S, R> = (store: Store<S>, payload?: any) => any;type Commit = (type: string, payload?: any) => void;class Store<S> {// 和CustObjType形成一个对比state!: S;commit!: Commit;constructor(storeOptions: StoreOptions<S>) {}}function createStore<S>(storeOptions: StoreOptions<S>) {return new Store<S>(storeOptions);}let storeOptions: StoreOptions<string> = {state: "lisi",actions: {searchFoodHistory({ commit }, payload) {},searchFindInfo() {},},};let store = createStore(storeOptions);export {};
