TS 高级类型

506 阅读7分钟

infer

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

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

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

type inferType<T> = T extends (param: infer P) => any ? P : T

interface Customer {
  custname: string
  buymoney: number
}

type custFuncType = (cust: Customer) => void

type inferType = inferType<custFuncType>// 获取到函数参数的类型 Customer
const cust: inferType = { custname: "wangwdu", buymoney: 23 }

  1. infer 举例2:
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
// 获取 Set 的指定泛型的类型
type ElementOf0<T> = T extends Set<infer E> ? E : never 
  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 + "班级:" + this.classno);
  }
}

// 获取构造函数 T 的参数类型 P,P的类型会在执行时候确定
type ConstructorParametersType<T extends new (...args: any[]) => any>
  = T extends new (...args: infer P) => any ? P : never

// 通用的构造器类型,T 是构造函数返回对象的类型
type Constructor<T> = new (...args: any[]) => T

// 根据构造函数类型,以及传入的参数,创建一个实例对象
function createInstance<T, C extends new (...args: any[]) => any>(constructor: Constructor<T>,
  ...args: ConstructorParametersType<C>) {
  return new constructor(args[0], args[1])
}
type classType = typeof TestClass
createInstance<TestClass, classType>(TestClass, "wangwu", 105).eat();
createInstance(TestClass, ["wangwu", 23])
  1. 联合 Vue3 源码 理解 infer
// 类似于 infer 举例2的用法
function unref<T>(ref: T): T extends Ref<infer V> ? V : T {
  return isRef(ref) ? (ref.value as any) : ref
}

Extract 和 Exclude

  1. 详解 Extract.ts
// Extract 类型定义格式
type Extract<T, U> = T extends U ? T : never

class People {
  public name!: string;
  public age!: number
  public address!: string
  eat() {

  }
}

class ChinesePeople extends People {
  private phone!: string

}

let cp = new ChinesePeople();

// Extract 是TS提供的一个TS高级type类型【简称TS高级类型】
type Extract<T, U> = T extends U ? T : never

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

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

  1. 从结果上详细对比的 Extract 泛型约束和类型断言【父子类】
// 类型断言 在父类和子类如何断言
let people: People = new People();
let ChinesePeople2 = people as ChinesePeople// 父类对象变量断言成子类类型 成立

let americanPeople: ChinesePeople = new ChinesePeople();
let p: People = americanPeople as People;// 子类对象变量断言成父类类型 成立
  1. 从结果上详细对比 Extract 泛型约束和类型断言【联合类型】
type Extract<T, U> = T extends U ? T : never

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


type beginType3 = string extends string | number ? string : never// string

type beginType4 = number extends string | number ? number : never// number

type extractUnionType3 = Extract<string, string | number>//string
type extractUnionType4 = Extract<number, string | number>//number
  1. 从结果上详细对比 Extract 泛型约束和类型断言 【函数】
// 函数的泛型约束
// 函数类型上的泛型约束 参数类型和返回值完全相同的情况下,
// 参数少的函数类型 extends 参数多的函数类型 返回true
// 参数多的函数类型 extends 参数少的函数类型 返回false
type beginType1 = func1 extends func2 ? func1 : never// never
type beginType2 = func2 extends func1 ? func2 : never// (one: number) => string

type extractType1 = Extract<func1, func2>//never
type extractType2 = Extract<func2, func1>//= (one: number) => string
  1. Extract 真实应用场景.ts
type Extract<T, U> = T extends U ? T : never
type CrosTyp<T> = Extract<T, object>
// 简化写法,不用一直 泛型 extends object 进行泛型约束了
function cross<T, U, V>(objOne: CrosTyp<T>, objTwo: CrosTyp<U>, objThree?: CrosTyp<V>)
  1. Exclude
// 写法
type Exclude<T, U> = T extends U ? never : T

interface Worker {
  name: string
  age: number
  email: string
  salary: number
}

interface Student {
  name: string
  age: number
  email: string
  grade: number
}
// 用Extract来完成的获取Worker接口类型中的"age" | "email" | "salary"三个属性组成的联合类型
type Extract<T, U> = T extends U ? T : never
type isResultType = Extract<"age" | "email" | "salary" | "xx", keyof Worker>

//排除条件成立的类型,保留不符合泛型约束条件的类型
type Exclude<T, U> = T extends U ? never : T
// 用Exclude来完成的获取Worker接口类型中的"age" | "email" | "salary"三个属性组成的联合类型
type isResultType2 = Exclude<"age" | "email" | "salary" | "xx", keyof Worker>//xx
type isResultType22 = Exclude<"name" | "xx", keyof Worker>//xx
type isResultType23 = Exclude<"name", keyof Worker>//never
type isResultType24 = Exclude<"name" | "age" | "email" | "salary", "name">// "age" | "email" | "salary"

// 获取Woker接口类型中存在的属性,但是在学生接口类型中不存在的属性
type isResultType25 = Exclude<keyof Worker, keyof Student>//salary

Record

  1. K extends keyof any 等价于 K extends keyof string |number |symbol
  2. K in keyof any 等价于 [x: string]: ,可以接number 或 symbol,自动转成字符串。如果写成这种形式,就不能接收数组了。
  3. 写法
type Customer = {
    custname: string,
    age: number
    phone: string
}

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

type resultRecord = Record<string, Customer>//S100
let obj: resultRecord =
{
    "usernamed": { custname: "wangwu", age: 23, phone: "111" },
    "agde": { custname: "lisi", age: 33, phone: "23" }
}

// 如果传入number类型,就可以接数组
type resultRecord2 = Record<number, Customer>//S100
let objarray: resultRecord2 = [{ custname: "wangwu", age: 23, phone: "111" },
{ custname: "lisi", age: 33, phone: "23" }]
  1. Record 完成数据扁平化
type Record<K extends keyof any, T> = {
  [P in K]: T
}
let goodRecord: Record<number, Goods> = {}
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
  }
]

goodsList.map((goods) => {
  goodRecord[goods[goodSymid]] = goods;
})
// 泛型自动提示
console.log("goodRecord:", goodRecord);
for (let key in goodRecord) {
  console.log(goodRecord[key].name)
}
// type Record2<T> = {
//   [x: string]: T,// 字符串索引可以是数字类型,可以是字符串类型,最终都会转换为字符串类型
//   //[x: number]: T,// 字符串索引可以是数字类型 [x: number]可以最终合成一个数组的索引
//   //[x:symbol]:T//索引签名参数类型必须为 "string" 或 "number"
// }
  1. object 和 Record
    • 区别1:Record 获取到是索引参数类型,所以可以赋初值为{} 。object也可以,但是再次赋值,比如: goodRecord[103] = good2;会出现错误,会查找103属性是否存在于object类型的对象变量中
    • 区别2:Record是泛型,获取值可以有自动提示功能,而object无法实现自动提示。
  2. object 相比 Map 的好处
    • Record有多种实现方式,比如S100实现方式,Map就需要改底层源码才能做到【一般是不会改的】
    // type Record<T> = {// S100
    [P in keyof any]: T
    }
    
    • Record是属于一个轻量级的type类型, Map相对Record是重量级,而且Map需要new出来的,所以要更加占用内存空间。如果读取数据和显示数据频繁,就应该采用Record,如果增删改比较多,那还是使用Map。

Pick

  1. Pick 主要用于提取某种数据类型的属性,但实际工作中,主要用来提取接口或 type 定义的对象类型中的属性
  2. Pick+ Record 结合应用
// 理解 Pick
// 而 keyof用来获取接口的属性名【key】组成的联合类型  
//  K 如果 属于 keyof T 联合类型或者它的子类型
//  那么 K extends keyof T就成立
type Pick<T, K extends keyof T> = {
  // in是类型映射,=for...in 循环迭代所有的K的类型
  [P in K]: T[P]
}

const todonew: Pick<Todo, "title"> = {
  "title": "下午3点美乐公园参加party"
}
const todonew2: Pick<Todo, "title" | "completed"> = {
  "title": "下午3点美乐公园参加party",
  "completed": false
}


interface Todo {
  title: string
  completed: boolean
  description: string
}


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

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

const todo2: Pick<Todo, "title" | "completed"> = {
  title: 'Clean room',
  completed: false
}
export { }

Partial、Required、ReadOnly

// Partial 一次性全部变成可选选项的type高级类型
type Partial<T> = {
  [P in keyof T]?: T[P]
}
interface ButtonProps {
  type: 'button' | 'submit' | 'reset'
  text: string
  disabled: boolean
  onClick: () => void
}

let props: Partial<ButtonProps> = {
  text: "登录"
}
    
// Required 和Partial相反 一次性全部变成必选选项的type高级类型
 type Required<T> = {
  [P in keyof T]-?: T[P]
}

//  ReadOnly 一次性全部变成可读选项的type高级类型
type ReadOnly<T> = {
  readonly [P in keyof T]: T[P]
}

Omit 反向抓取属性数据

和 Pick 正好相反

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

interface Todo {
  title: string
  completed: boolean
  description: string
  // phone: number
}

type TodoPreview = Omit<Todo, "description">//type TodoPreview={}

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

export { }