1. 高级类型的价值

TypeScript提供了较多的高级类型,通过学习高级类型可以帮助提高TS代码的灵活运用能力,掌握好这些高级类型能进一步提升我们对泛型的理解和驾驭能力,让TS功底更深厚,把我们的TS水平推向一个更高的层次,无论以后在项目中运用TS还是对理解源码的复杂TS泛型语法都有不小的帮助。
由于TS高级类型为我们提供了很多技巧性强的功能, 当我们在项目中遇到使用这些功能的应用场景时,会给项目带来更简洁,更轻量级的实现效果,比如:如果我们项目中只需要查询 key value 数据,那么Record类型就是轻量级的Map,再比如Omit快捷爬取Todo列表中的数据,保证编辑和预览时的不同效果。

2. infer

:::info infer的定义:infer表示extends条件语句中以占位符出现的用来修饰数据类型的关键字,被修饰的数据类型等到使用时才能被推断出来。 ::: infer占位符式的关键字出现的位置

  1. extends条件语句后的函数类型的参数类型位置上
  2. extends条件语句后的函数类型的返回值类型上
  3. 泛型的具体化类型上 ```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 = T extends Set ? E : never class Subject { constructor(public subid: number, public subname: string) { } } let chineseSubject = new Subject(100, “语文”) let mathSubject = new Subject(101, “数学”) let englishSubject = new Subject(101, “英语”) let setZhangSanSubject = new Set([chineseSubject, mathSubject]); let result: ElementOf0; //解析得到Set中元素的类型

  1. vue3中的infer
  2. ```typescript
  3. function unref<T>(ref: T): T extends Ref<infer V> ? V : T {
  4. return isRef(ref) ? (ref.value as any) : ref;
  5. }

3. infer构建带参数的工厂函数

使用infer获得构造函数的参数类型,然后利用该参数类型进一步限制工厂函数的参数类型,代码更加严谨。

  1. type ConstructorParamsType<T extends new (...args: any[]) => any> =
  2. T extends new (...args: infer P) => any ? P : never;
  3. type Constructor<T> = new (...args: any[]) => T;
  4. function createInstance<T, P extends new (...args: any[]) => any>(
  5. constructor: Constructor<T>,
  6. ...args: ConstructorParamsType<P>
  7. ) {
  8. return new constructor(...args);
  9. }
  10. let instance = createInstance<TestClass, typeof TestClass>(
  11. TestClass,
  12. "xiaoming",
  13. 23
  14. );
  15. console.log(instance.name);

4. Extract

:::info Extract相当于TS为我们提供了这样一个类型:
type Extract<T, U> = T extends U ? T : never ::: extends有以下特点

  1. 在父子类中:子类 extends 父类返回true父类 extends 子类一般为false,除非父类和子类的实例属性和方法(无论是public、private还是protected)都和父类完全一致。
  2. 在联合类型中:TS会将T分解和U比较并合成得出结果。如Extract<string | number, string>,第一步:string extends string ? T : never得到string;第二步number extends string ? T : never得到never,将string和never合并,得出最后的结果never。
  3. 在函数中:函数类型上的泛型约束,在参数类型和返回值完全相同的情况下,参数少的函数类型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 = T extends U ? T : never;

// 1.父子类中 type extractType = Extract; // ChinesePeople type extractType2 = Extract; // never

// 2.联合类型中 type unionExtractType = Extract; // string type unionExtractType2 = Extract; // string type unionExtractType3 = Extract; // string

// 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 // never type extractType2 = Extract // (one: number) => string

  1. 【应用】可以简化<T extends object>这样的泛型,例如:
  2. ```typescript
  3. function cross<T, U>(obj1: Extract<T, object>, obj2: Extract<U, object>): T & U;
  4. function cross<T, U, V>(obj1: Extract<T, object>,obj2: Extract<U, object>,obj3: Extract<V, object>): T & U & V;
  5. function cross<T, U, V>(obj1: Extract<T, object>,obj2: Extract<U, object>,obj3?: Extract<V, object>): any {
  6. ...
  7. }

5. ⭐Exclude

:::info Exclude相当于TS为我们提供了这样一个类型:
type Exclude<T, U> = T extends U ? never : T :::

  1. interface MyWorker {
  2. name: string;
  3. age: number;
  4. email: string;
  5. salary: number;
  6. }
  7. interface Student {
  8. name: string;
  9. age: number;
  10. email: string;
  11. grade: number;
  12. }
  13. // 获取MyWorker中的"age" | "email" | "salary",使用Exclude更方便
  14. type resultType = Extract<"age" | "email" | "salary" | "xx", keyof MyWorker>; //"age" | "email" | "salary"
  15. type resultType2 = Exclude<"name" | "xx", keyof MyWorker>; // xx
  16. type resultType3 = Exclude<keyof MyWorker, "name">; // "age" | "email" | "salary"
  17. // 获取MyWorker中有,但学生中没有的属性
  18. type resultType4 = Exclude<keyof MyWorker, keyof Student>; // "salary"
  19. // 获取MyWorker和Student中共同的属性
  20. type resultType5 = Extract<keyof MyWorker, keyof Student>;

:::warning 【区别】
Extract排除条件不成立的类型,保留符合泛型约束条件的类型
Exclude排除符合泛型约束条件的,保留条件不成立的类型 :::

6. Record

K extends keyof T的使用

  1. type Worker = {
  2. custname: string;
  3. };
  4. type Customer = {
  5. custname: string;
  6. age: number;
  7. };
  8. type oneType<T, K> = K extends keyof T ? K : never;
  9. type oneTypeResult = oneType<Customer, "custname">; // 输出custname类型
  10. type twoType<T, K> = K extends keyof T ? T[K] : never;
  11. type twoTypeResult = twoType<Customer, "age">; // 输出number

:::info Record相当于
type Record<K extends keyof any, T> = {[P in K]: T}
其中type keyof any = string | number | symbol :::

  1. const goodSymid = Symbol("goodid");
  2. interface Goods {
  3. [goodSymid]: number;
  4. name: string;
  5. price: number;
  6. }
  7. const goodsList: Goods[] = [
  8. {
  9. [goodSymid]: 101,
  10. name: "苹果",
  11. price: 9,
  12. },
  13. {
  14. [goodSymid]: 102,
  15. name: "香蕉",
  16. price: 3,
  17. },
  18. {
  19. [goodSymid]: 103,
  20. name: "橘子",
  21. price: 3,
  22. },
  23. ];
  24. // 实现方式
  25. let goodRecord: Record<number, Goods> = {};
  26. goodsList.forEach((good) => {
  27. goodRecord[good[goodSymid]] = good;
  28. });
  29. console.log("goodRecord:", goodRecord);

:::warning Record和Object、Map的区别 :::

  1. // 定义Goods接口
  2. const goodSymid = Symbol("goodid")
  3. interface Goods {
  4. [goodSymid]: number
  5. name: string
  6. price: number
  7. }
  8. type Record<T> = {[P in keyof any]: T}
  9. // Record和object区别
  10. // 区别1:Record 获取到是索引参数类型,所以可以赋初值为{}
  11. // 而object也可以,但是再次赋值,比如: goodRecord[103] = good2;
  12. // 会出现错误,会查找103属性是否存在于object类型的对象变量中
  13. // 区别2: Record是泛型,获取值可以有自动提示功能,而object无法实现自动提示。
  14. type resultGoodsType = Record<Goods>
  15. let good: Goods = { [goodSymid]: 101, "name": "苹果", "price": 9 }
  16. // let obj={} 这样一种情况
  17. // let goodRecord = { name: "wangwu", 103: good }
  18. // let good2: Goods = { [goodSymid]: 101, "name": "苹果", "price": 9 }
  19. // goodRecord[103] = good2;// 修改103的值为good2,而不能增加103属性名[key],js可以增加
  20. // goodRecord["香蕉"] = good
  21. // goodRecord[good[goodSymid]] = good
  22. let goodRecord: Object = { name: "wangwu", 103: good }
  23. let good2: Goods = { [goodSymid]: 101, "name": "苹果", "price": 9 }
  24. // 编译器尝试从object上查找103属性,
  25. // 但object什么属性和方法都没有,只是单纯表示一个对象的类型
  26. // Object也不行,因为也没有103属性,直接抛出 类型“Object”上不存在属性“103”。ts(7053)
  27. // goodRecord[103] = good2;// 修改103的值为good2,而不能增加103属性名【key],js可以增加
  28. // goodRecord["香蕉"] = good
  29. // goodRecord[good[goodSymid]] = good
  30. // console.log("goodRecord:", goodRecord);
  31. // // Record类型对于取出来的对象,可以自动提示输出对象的属性和方法
  32. // for (let goodid in goodRecord) {
  33. // let good = goodRecord[goodid];
  34. // console.log(goodid, ":", good)
  35. // }
  1. // Record和object,Map区别
  2. // 定义Goods接口
  3. const goodSymid = Symbol("goodid")
  4. interface Goods {
  5. [goodSymid]: number
  6. name: string
  7. price: number
  8. }
  9. type Record<T> = { [P in keyof any]: T }
  10. // 实际开发为什么我们在显示数据,数据扁平化时用Record
  11. // 原因1:是因为Record有多种实现方式,比如S100实现方式,Map就需要改底层源码才能做到【一般是不会改的】
  12. // type Record<T> = {[P in keyof any]: T}
  13. // 原因2:Record是属于一个轻量级的type类型,Map相对Record是重量级
  14. // 而且Map需要new出来的,所以要更加占用内存空间
  15. // 如果读取数据和显示数据频繁,就应该采用Record
  16. // 如果增删改比较多,那还是使用Map
  17. type resultGoodsType = Record<Goods>
  18. let goodMap = new Map<any, Goods>();
  19. let good: Goods = { [goodSymid]: 101, "name": "苹果", "price": 9 }
  20. goodMap.set(103, good)
  21. goodMap.set("香蕉", good)
  22. goodMap.set(good[goodSymid], good);
  23. // Record类型对于取出来的对象,可以自动提示输出对象的属性和方法
  24. // for (let goodid in goodRecord) {
  25. // let good = goodRecord[goodid];
  26. // console.log(goodid, ":", good)
  27. // }

7. Pick

:::info Pick主要用于提取某种数据类型的属性,但实际工作中,主要用来提取接口或type定义的对象类型中的属性。
type Pick <T, K extends keyof T> = { [P in K] : T[P] }; ::: 基本使用

  1. interface Book {
  2. ISBN: string;
  3. book_name: string;
  4. book_price: string;
  5. book_store_count: number;
  6. book_publish: string;
  7. }
  8. type myBook = Pick<Book, "book_name" | "ISBN" | "book_price">;
  9. //结果为:
  10. type myBook = {
  11. book_name: string;
  12. ISBN: string;
  13. book_price: string;
  14. };
  1. interface Todo {
  2. title: string;
  3. completed: boolean;
  4. description: string;
  5. }
  6. const todonew: Pick<Todo, "title"> = {
  7. title: "下午3点美乐公园参加party",
  8. };
  9. const todonew2: Pick<Todo, "title" | "completed"> = {
  10. title: "下午3点美乐公园参加party",
  11. completed: false,
  12. };

8. ⭐Omit

Omit是为了反向抓取数据

  1. type Omit<T, K extends string | number | symbol>
  2. = { [P in Exclude<keyof T, K>]: T[P]; }

也类似于以下type,不同之处为Omit对K是否继承keyof T没有要求。

  1. type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
  2. interface Todo {
  3. title: string;
  4. completed: boolean;
  5. description: string;
  6. }
  7. type TodoPreview = Omit<Todo, "description">;
  8. type TodoPreview = {
  9. title: string;
  10. completed: boolean;
  11. };

9. Required & Partial & ReadOnly

:::info Partial<T>一次性全部变成可选选项的高级类型
Required<T>一次性全部变成必选选项的高级类型
ReadOnly<T>一次性全部变成只读选项的高级类型 :::

  1. type Partial<T> = {
  2. [P in keyof T]?: T[P]
  3. }
  4. type Required<T> = {
  5. [P in keyof T]-?: T[P]
  6. }
  7. type ReadOnly<T> = {
  8. readonly [P in keyof T]: T[P]
  9. }


10. 复杂面试题

  1. interface Action<T = any> {
  2. type: string;
  3. payload?: T;
  4. }
  5. class FoodModule {
  6. public static topic: string;
  7. public count!: number;
  8. delay(promise: Promise<number>) {
  9. return promise.then((second: number) => ({
  10. type: "delay",
  11. payload: `延迟${second}秒`,
  12. }));
  13. }
  14. searchFoodByCity(action: Action<string>) {
  15. return {
  16. payload: action.payload,
  17. type: "searchFoodByCity",
  18. };
  19. }
  20. }
  21. 【实现1FoodModule参数类型和返回值类型被部分过滤,如下:
  22. // 过滤后的delay方法类型
  23. type asyncMethodConnect<T, U> = (input: T) => Action<U>
  24. // 过滤后的searchFoodByCity方法类型
  25. type asyncMethodConnect<T, U> = (action: T) => Action<U>
  26. 【实现2】就是把这两个类型和原来的FoodModule类合并成如下对象
  27. type Convert = () => {
  28. delay: asyncMethodConnect<number, string>;
  29. searchFoodByCity: asyncMethodConnect<string, string>;
  30. }

11. Promise源码片段

主要关注Promise构造器的类型

  1. type ResolveType = (resolve: any) => any;
  2. type RejectType = (reject: any) => any;
  3. class MyPromise {
  4. public resovleFunc!: ResolveType;
  5. public rejectFunc!: RejectType;
  6. constructor(
  7. promiseParams: (resolve: ResolveType, reject: RejectType) => any
  8. ) {
  9. this.resovleFunc = (resolve: any): any => {
  10. console.log(resolve);
  11. };
  12. this.rejectFunc = (reject: any): any => {
  13. console.log(reject);
  14. };
  15. promiseParams(this.resovleFunc, this.rejectFunc);
  16. }
  17. }
  18. new MyPromise(function (resovle: ResolveType, reject: RejectType): any {
  19. resovle("success");
  20. });

12. Vuex源码片段

  1. interface StoreOptions<S> {
  2. state: S;
  3. actions: ActionTree<S, S>;
  4. }
  5. interface ActionTree<S, R> {
  6. [key: string]: ActionHandler<S, R>;
  7. }
  8. type ActionHandler<S, R> = (store: Store<S>, payload?: any) => any;
  9. type Commit = (type: string, payload?: any) => void;
  10. class Store<S> {
  11. // 和CustObjType形成一个对比
  12. state!: S;
  13. commit!: Commit;
  14. constructor(storeOptions: StoreOptions<S>) {}
  15. }
  16. function createStore<S>(storeOptions: StoreOptions<S>) {
  17. return new Store<S>(storeOptions);
  18. }
  19. let storeOptions: StoreOptions<string> = {
  20. state: "lisi",
  21. actions: {
  22. searchFoodHistory({ commit }, payload) {},
  23. searchFindInfo() {},
  24. },
  25. };
  26. let store = createStore(storeOptions);
  27. export {};