TypeScript最佳实践(七)高级类型

113 阅读6分钟

1. infer

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

1. infer例子

infer 占位符式的关键字出现的位置:通常infer出现在以下三个位置上。

  1. infer 出现在 extends 条件语句后的函数类型的参数类型位置上
  2. infer 出现在 extends 条件语句后的函数类型的返回值类型上
  3. infer 会出现在类型的泛型具体化类型上。

infer 举例1:

// 1)infer 出现在 extends 条件语句后的函数类型的参数类型位置上
interface Customer {
    custname: string
    buymoney: number
}

type custFuncType = (cust: Customer) => string
type inferType<T> = T extends (params: infer P) => any ? P : T
type inferResultType = inferType<custFuncType>

infer 举例2:

// 2)infer 出现在 extends 条件语句后的函数类型的返回值类型上
interface Customer {
    custname: string
    buymoney: number
}

type custFuncType = (cust: Customer) => string // 函数类型 
type inferType<T> = T extends (params: any) => infer P ? P : T
type inferResultType = inferType<custFuncType> // 输出函数的返回值类型string

infer 举例3:

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]);
type ss = typeof setZhangSanSubject
type ElementOf0<T> = T extends Set<infer E> ? E : never

2. 【 infer 】构建带参数的工厂实例方法

1. 获取构造函数参数

class TestClass {// 准备类
    public name: string
    public classno: number
    constructor(name: string, classno: number) {
        this.name = name;
        this.classno = classno
    }
    eat() {
        console.log("姓名为: " + this.name);
    }
}

type ConstructorParametersType<T extends new (...args: any[]) => any>
  = T extends new (...args: infer P) => any ? P : never
  
let constructorParameters: ConstructorParametersType<typeof TestClass>

2. 获取类实例

class TestClass {// 准备类
    public name: string
    public classno: number
    constructor(name: string, classno: number) {
        this.name = name;
        this.classno = classno
    }
    eat() {
        console.log("姓名为: " + this.name);
    }
}

type InstanceType<T extends new (...args: any[]) => any>
  = T extends new (...args: any) => infer P ? P : never
  
let instanceType: InstanceType<typeof TestClass>

3. 创建带参数的构造函数工厂实例方法

type Constructor<T> = new (...args: any[]) => T

function createInstance<T>(constructor: Constructor<T>, ...args: any[]) {
    return new constructor(args[0], args[1])
}
// createInstance<TestClass>(TestClass, "wangwu", 105).eat();
createInstance(TestClass, "wangwu", 105).eat();

2. Exclude(排除)

Exclude<T, U> 的作用是将某个类型中属于另一个的类型移除掉

1. demo

type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number

2. 实现

如果 T 能赋值给 U 类型的话,那么就会返回 never 类型,否则返回 T 类型。最终实现的效果就是将 T 中某些属于 U 的类型移除掉

type MyExclude<T, U> = T extends U ? never : T;

3. Extract(提取)

Extract<T, U> 的作用是将某个类型中属于另一个的类型提取出来

1. demo

type beginType1 = string | number extends string ? string | number : never// never
type extractUnionType = Extract<string | number, string>//string
type extractUnionType2 = Extract<string | number, number>//number

type extractUnionType3 = Extract<string, string | number>//string
type extractUnionType4 = Extract<number, string | number>//number

这里为什么beginType1never类型而不是string | number类型。

2. 实现

type Extract<T, U> = T extends U ? T : never

3. Extract 在 父类和子类中应用

class People {
    public name!: string;
    public age!: number
    public address!: string
}

class ChinesePeople extends People {
    private phone!: string
}

type Extract<T, U> = T extends U ? T : never

// 定律:子类 extends 父类, 永远返回true=> 返回T类型
type extractType = Extract<ChinesePeople, People> // ChinesePeople

// 定律: 父类 extends 子类返回false 因为父类继承子类本身不成立,所以一般都为false
// 但如果希望人为制造一个true 获取到People, 那只有子类实例属性或实例方法个数必须和父类一样多
type extractType2 = Extract<People, ChinesePeople> // never

4.函数的泛型约束

函数类型上的泛型约束,参数类型和返回值完全相同的情况下:

  • 参数少的函数类型 extends 参数多的函数类型 返回true
  • 参数多的函数类型 extends 参数少的函数类型 返回false
type func1 = (one: number, two: string) => string
type func2 = (one: number) => string

// 函数类型上的泛型约束 参数类型和返回值完全相同的情况下,
// 参数少的函数类型 extends 参数多的函数类型 返回true
// 参数多的函数类型 extends 参数少的函数类型 返回false
type beginType1 = func1 extends func2 ? func1 : never// never
type beginType2 = func2 extends func1 ? func2 : never// never

type extractType1 = Extract<func1, func2>//never
type extractType2 = Extract<func2, func1>//= (one: number) => string

4. Pick

Pick<T, K extends keyof T> 的作用是将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。

type MyPick<T, K extends keyof T> = { 
    [P in K]: T[P];
};
interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Pick<Todo, "title" | "completed">;

const todo: TodoPreview = { 
    title: "Clean room", 
    completed: false
};

5. Omit

Omit<T, K extends keyof T> 的作用是将某个类型中的子属性排除。

type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Pick<Todo, "description">;

const todo: TodoPreview = { 
    title: "Clean room", 
    completed: false
};

6. ReturnType

ReturnType<T> 的作用是用于获取函数 T 的返回类型。

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
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

7. InstanceType

InstanceType<T>用来获取类的实例类型

InstanceType: zhuanlan.zhihu.com/p/52662645

type InstanceType<T extends new (...args: any[]) => any>
  = T extends new (...args: any) => infer P ? P : never
class TestClass {// 准备类
    public name: string
    public classno: number
    constructor(name: string, classno: number) {
        this.name = name;
        this.classno = classno
    }
    eat() {
        console.log("姓名为: " + this.name);
    }
}
  
let instanceType: InstanceType<typeof TestClass>

8. Recode

Record<K extends keyof any, T> 的作用是将 K 中所有的属性的值转化为 T 类型

type Record<K extends keyof any, T> = { [P in K]: T; };

1. 扁平化数据

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
  }
]

type Record<K extends keyof any, T> = {
  [P in K]: T
}

let goodRecord: Record<number, Goods> = {}
goodsList.forEach((goods) => {
    goodRecord[goods[goodSymid]] = goods;
})
// //goodRecord: {
//   '101': { name: '苹果', price: 9, [Symbol(goodid)]: 101 },
//   '102': { name: '香蕉', price: 3, [Symbol(goodid)]: 102 },
//   '103': { name: '橘子', price: 3, [Symbol(goodid)]: 103 } 
// }

for (let goodid in goodRecord) {
    console.log(goodid, ":", goodRecord[goodid])
}
const goodSymid = Symbol("goodid")
interface Goods {
    [goodSymid]: number
    name: string
    price: number
}

type Record<T> = {
  [P in keyof any]: T
}

type resultGoodsType = Record<Goods>
let goodRecord: Record<Goods> = {}
let good: Goods = { [goodSymid]: 101, "name": "苹果", "price": 9 }

goodRecord[103] = good;
goodRecord["香蕉"] = good
goodRecord[good[goodSymid]] = good
console.log("goodRecord:", goodRecord);

// Record类型对于取出来的对象,可以自动提示输出对象的属性和方法
for (let goodid in goodRecord) {
    let good = goodRecord[goodid];
    console.log(goodid, ":", good)
}

2. Recode与Map区别

实际开发为什么我们在显示数据,数据扁平化时用Record

原因1:是因为Record有多种实现方式,比如S100实现方式,Map就需要改底层源码才能做到【一般是不会改的】

原因2:Record是属于一个轻量级的type类型,Map相对Record是重量级,而且Map需要new出来的,所以要更加占用内存空间

  • 如果读取数据和显示数据频繁,就应该采用Record
  • 如果增删改比较多,那还是使用Map

9. Required

Required<T>的作用就是将某个类型里的属性全部变为必选

type Required<T> = {
    [P in keyof T]-?: T[P];
}

原来在 Required<T> 工具类型内部,通过 -? 移除了可选属性中的 ? ,使得属性从可选变为必选的

10. Partial

Partial<T> 的作用就是将某个类型里的属性全部变为可选项 ?

type Partial<T> = {
    [P in keyof T]?: T[P];
};

在以上代码中,首先通过 keyof T 拿到 T 的所有属性名,然后使用 in 进行遍历,将值赋给 P ,最 后通过 T[P] 取得相应的属性值。中间的 ? 号,用于将所有属性变为可选。

11. ReadOnly

ReadOnly 一次性全部变成可读选项

type ReadOnly<T> = {
    readonly [P in keyof T]: T[P]
}

12. 特殊符号

1. ! 非空断言操作符

1. 忽略undefined和null

2. 调用函数忽略undefined

3. 确定赋值断言

2. ?. 可选链

3. ?? 空值合并运算符

当左侧操作数为 null 或 undefined 时,其返回右侧的操作数,否则返回左侧的操作数。

4. ?: 可选属性

5. _ 数字分隔符

6. @ 装饰器

7. # 私有字段