详解TS中的 infer 与高阶泛型类型

一. infer

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

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

  1. infer 出现在extends 条件语句后的函数类型的参数类型位置上
ts 复制代码
interface Customer {
  custname: string
  buymoney: number
}

// 1
type custFuncType = (cust: Customer) => void
type inferType<T> = T extends (x: infer P) => any ? P : T

type inferRes = inferType<custFuncType> // Customer


// 2
type custFuncType = (cust: Customer) => number
type inferType<T> = T extends (x: infer P) => string ? P : T

type inferRes = inferType<custFuncType> // (cust: Customer) => number
  1. infer 出现在 extends 条件语句后的函数类型的返回值类型上
ts 复制代码
// 1
interface Customer {
  custname: string
  buymoney: number
}

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

type inferRes = inferType<custFuncType> // string

// 2
interface Customer {
  custname: string
  buymoney: number
}

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

type inferRes = inferType<custFuncType> // (cust: Customer) => string
  1. infer 会出现在类型的泛型具体化类型上。
ts 复制代码
class Subject {
  constructor(public num: number, public project: string) {}
}

let chineseSub = new Subject(100, '语文')
let matchSub = new Subject(100, '数学')
let englishSub = new Subject(100, '英语')
// 此处的Subject作为类型使用
let setSub = new Set<Subject>([chineseSub, matchSub, englishSub])
// 此处的Subject作为变量使用
type ss = typeof setSub
type eleType<T> = T extends Set<infer V> ? V : never

let res: eleType<ss>

export {}

二. 类的类型定义

ts 复制代码
class Person {}

// 通用
type common<T> = new (...args: any[]) => T
let mm: common<Person> = Person

// 1
let p1: new (name: string, phone: number, notional: string) => Person = Person

// 2
let p2: new (...args: any[]) => any = Person

// 3
let p3: typeof Person = Person

三. 泛型定义类工厂函数

ts 复制代码
class TestClass {
  constructor(public name: string, public age: number) { }
  eat() {
    console.log(this.name + 'haha');
  }
}

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

type ArgumentsType<T extends new (...args: any[]) => any> = T extends new (...args: infer R) => any ? R : never
// args = [name: string, age: number] 元组
function createInstance<T, V extends new (...args: any[]) => any>(Constructor: ConstructorType<T>, ...args: ArgumentsType<V>) {
  return new Constructor(...args)
}

let p = createInstance<TestClass, typeof TestClass>(TestClass, 'kk', 18)
p.eat() // kkhaha

四. Ref的infer应用(unref)

ts 复制代码
function unref<T>(ref: T): T extends Ref<infer V> ? V : T {
  return isRef(ref) ? (ref.value as any) : ref
}
// 基本类型
let baseRef = ref(23) // { value: 23 }
// 一个基本类型会转化为Ref<number>
// let baseUnref = unref<Ref<number>>(baseRef)
let baseUnref = unref(baseRef)
// 对象
let objectRef = ref({ name: 'kk' })
let objectUnref = unref(objectRef)

// 当使用对象作为参数时,在使用ref时不会将对象转化为Ref对象,所以直接返回

isRef 类型判断的实现

五. 高阶类型

1. Extract

排除不成立的,寻找重合部分

r 复制代码
type Extract<T, U> = T extends U ? T : never
  • 父子类

定律一:子类extends 父类=》子类extends 父类返回true=>返回T类型

ts 复制代码
class People {
  public name: string = 'jj'
}

class ChinesePeople extends People {
  public addr: string = 'yy'
}

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

type extractType = Extract<ChinesePeople, People> // ChinesePeople

定律二:父类 extends 子类=>父类 extends 子类返回false 因为父类继承子类本身不成立

但如果希望人为制造一个true 获取到People

那只有子类实例属性或实例方法个数必须和父类一样多

ts 复制代码
class People {
  public name: string = 'jj'
}

class ChinesePeople extends People {
  public addr: string = 'yy'
}

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

type extractType = Extract<People, ChinesePeople> // never

如果两个类都为可选属性,那么如果父类extends子类也成立:

ts 复制代码
class People {
  public name?: string = 'jj'
}

class ChinesePeople extends People {
  private age?: number
}

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

type extractType = Extract<People, ChinesePeople> // People

或者属性一致

ts 复制代码
class People {
  public name: string = 'jj'
}

class ChinesePeople extends People {
  public name: string = 'jj'
  // private age?: number
  // public addr: string = 'yy'
}

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

type extractType = Extract<People, ChinesePeople> // People
  • 联合类型
ts 复制代码
// 取合集
type unionExtractType = Extract<string, string | number> // string

type unionExtractType1 = Extract<string | number, string | number | boolean> // string | number

另一种情况

ts 复制代码
type unionExtractType2 = Extract<string | number, string> // string

两个合集为或时,会挨个将左侧与右侧进行判断,例如:

ts 复制代码
string extends string ? // string
number extends string ? // never

所以返回 string

加入将Extract更改一下

ts 复制代码
// 将never更改为boolean
type Extract<T, U> = T extends U ? T : boolean

// string | number | boolean
type unionExtractType3 = Extract2<string | number | symbol, string | number> 

断言番外

ts 复制代码
function testFunc(one: string | number, two: string) {
    one as string
    one as number
    one as any
    // 单类型可以断言为联合类型
    two as string | number | boolean
}
  • 函数类型

在返回值相同时,参数有包含关系,第一个参数相同,参数少 extends 参数多成立

ts 复制代码
type func1 = (name: string, age: number) => string
type func2 = (name: string) => string

// (name: string) => string
type unionFunc = Extract<func2, func1>

Extract应用场景

泛型函数重载:(比较繁琐)

ts 复制代码
function cross<T extends object, U extends object>(obj1: T, obj2: U): T & U
function cross<T extends object, U extends object, V extends object>(obj1: T, obj2: U, obj3: V): T & U & V
function cross<T extends object, U extends object, V extends object>(obj1: T, obj2: U, obj3?: V) {}

使用Extract优化:

ts 复制代码
type Extract<T, U> = T extends U ? T : never

function 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>) {}

深层优化:

ts 复制代码
type Extract<T, U> = T extends U ? T : never
type CyosTyp<T> = Extract<T, object>

function cross<T, U>(obj1: CyosTyp<T>, obj2: CyosTyp<U>): T & U
function cross<T, U, V>(obj1: CyosTyp<T>, obj2: CyosTyp<U>, obj3: CyosTyp<V>): T & U & V
function cross<T, U, V>(obj1: CyosTyp<T>, obj2: CyosTyp<U>, obj3?: CyosTyp<V>) {}

2. Exclude

Extract相反,排除成立的,寻找差异部分

使用Exclude获取指定属性

ts 复制代码
interface People {
    name: string
    age: number
    addr: string
    male: boolean
}

// 使用keyof获取所有key
type exc1 = Exclude<keyof People, 'name'> // age | addr | male

获取Woker接口中存在,而Student接口不存在的属性

ts 复制代码
interface Student {
    name: string
    age: number
    addr: string
    male: boolean
}

interface Worker {
    name: string
    age: number
    addr: string
    salary: boolean
}

type exc1 = Exclude<keyof Student, keyof Worker> // 'salary'

3. Record

理解keyof

ts 复制代码
type dataType = {
  name: string,
  age: number
}
ts 复制代码
// 返回key
type oneType<T, U> = T extends keyof U ? T : never

type test = oneType<'name', dataType> // name


// 返回类型
type twoType<T, U> = T extends keyof U ? U[T] : never

type test = twoType<'name', dataType> // string

keyof any

ts 复制代码
type threeType<T> = T extends keyof any ? T : never

type anyType = keyof any // number | string | symbol
ts 复制代码
type Customer  = {
  name: string, 
  age: number
}

type oneRes = threeType<Customer> // never

type oneRes = threeType<number> // number
type oneRes = threeType<3> // 3     3 extends number true

let str = 'haha'
type oneRes = threeType<typeof str> // string
type oneRes = threeType<'xixi'> // xixi     xixi extends number true

let sym = Symbol('ui')
type oneRes2 = threeType<typeof sym> // symbol

Record

ts 复制代码
type Record<K extends keyof any, T> = {
  [P in K] : T
}
// in 代表遍历K的联合类型

约束对象的结构

ts 复制代码
// 约束key只能为number / string
type test2 = Record<string | number, Student>

如果传入第一个参数为string,那么代表形成一个key为字符串的索引类型

ts 复制代码
interface Student {
    name: string
    age: number
    addr: string
    male: boolean
}

type test1 = Record<string, Student>

如果传入第一个参数为number,那么代表形成一个key为数值的索引类型

ts 复制代码
interface Student {
    name: string
    age: number
    addr: string
    male: boolean
}

type test1 = Record<number, Student>

索引中字符串和数字类型可以进行转换:

[x: string]可以是字符串,数字,Symbol类型

ts 复制代码
// 1
type TTT  = {
    [x: string]: string
}

// 也可以是Symbol
const sym = Symbol('op')
let testObj: TTT  = {
    3: 'hha',
    as: '123',
    'rr': '900',
    sym: '123'
}

// 2
type TTT  = {
    [x: number]: string
}

let testObj: TTT  = {
    3: 'hha',
    '1': '123'
}

变种

ts 复制代码
type Record<K extends keyof any, T> = {
  [P in K] : T
}

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

因为索字符串引可以替代数字和Symbol类型,所以P会被解析为字符串索引:

可以用来约束数组,因为数组是以数字为索引的对象结构。

ts 复制代码
let arr: test1 = [{
    name: 'kk',
    age: 18,
    addr: 'ee',
    male: true
},
{
    name: 'kk',
    age: 13,
    addr: 'qe',
    male: true
}]

数组扁平化


目标:

css 复制代码
[{}, {}]
{
  1: {},
  2: {}
}
  • 实现方式一
ts 复制代码
const goodSymId = Symbol('goodid')
interface dataType {
    name: string,
    price: number,
    [goodSymId]: number
}

let goodList: dataType[] = [
    {
        name: '苹果',
        price: 3,
        [goodSymId]: 101
    },
    {
        name: '橘子',
        price: 6,
        [goodSymId]: 102
    },
    {
        name: '橙子',
        price: 9,
        [goodSymId]: 103
    }
]

type resGoodsType = Record<number, dataType>
let res: resGoodsType = {}

goodList.forEach(goods => {
    res[goods[goodSymId]] = goods
})
ts 复制代码
{
  "101": {
    "name": "苹果",
    "price": 3,
    [Symbol(goodid)]: 101
  },
  "102": {
    "name": "橘子",
    "price": 6,
    [Symbol(goodid)]: 102
  },
  "103": {
    "name": "橙子",
    "price": 9,
    [Symbol(goodid)]: 103
  }
}

Record和object区别

区别1: Record 获取到是索引参数类型,所以可以赋初值为{} 而object也可以,但是再次赋值会出现错误。

区别2:Record是泛型,获取值可以有自动提示功能,而object无法实现自动提示。

Map与Record区别

ts 复制代码
interface dataType {
    name: string,
    price: number,
    [goodSymId]: number
}

let data = new Map<string, dataType>()
data.set('key', {
        name: '苹果',
        price: 3,
        [goodSymId]: 101
    })

4. Pick

仅保留部分属性

ts 复制代码
type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}
ts 复制代码
interface Book {
  book_name: string,
  book_price: number,
  book_publish: string
}

type PickType = Pick<Book, 'book_name' | 'book_price'>
// {
//		book_name: string,
//    book_price: number,	
//}

5. Omit

ts 复制代码
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
ts 复制代码
interface Todo {
	title: string 
  completed: boolean 
  description: string
}

type test = Omit<Todo, 'description'>
// 拆解
type ExcludeType = Exclude<keyof Todo, 'description'> // 'title' | 'completed'

type PickType = Pick<Todo, 'title' | 'completed'>
// {
// 	title: string 
//   completed: boolean 
// }

模拟Promis定义ts类型

ts 复制代码
type ResolveType = (resolve_success: any) => any
type RejectType = (reject_fail: any) => any

class MyPromise {
  public resolveFunc!: ResolveType
  public rejectFunc!: RejectType
  constructor(promiseParams: (resolve: ResolveType, reject: RejectType) => any) {
    this.resolveFunc = (resolve_success: any): any => {
      console.log('success');
    }

    this.rejectFunc = (reject_fail: any): any => {
      console.log('fail');
    }
    promiseParams(this.resolveFunc, this.rejectFunc)
  }
}

new MyPromise(function (resolve: ResolveType, reject: RejectType) {
  resolve('success')
})

函数解构

ts 复制代码
type CustObjType = { custname: string, degree: number }
type funcType = (one: CustObjType, two: string) => void

function func(fn: funcType) {
  fn({ custname: 'kk', degree: 18 }, 'good')
}
// 解构使用CustObjType进行限制
func(function({ degree }: CustObjType, two: string) {
  console.log(degree);
})

vuex准备:实现action类型

不使用new来实例化类

ts 复制代码
class Store<S> {
  public state!: S
  commit!: CommitType
}

// let store: Store2<string> = {
let { state, commit }: Store2<string> = {
  state: 'gg',
  commit: (type: string, payload: any) => {}
}

简易actions

ts 复制代码
type CommitType = (type: string, payload?: any) => void
type ActionsHandler<S, R> = (store: Store<S>, payload?: any) => any
type ActionsTrss<S, R> = {
  [Key: string]: ActionsHandler<S, R>
}

class Store<S> {
  public state!: S
  commit!: CommitType
  constructor(storeOptions: StoreOptions<S>) { }
}

interface StoreOptions<T> {
  state: T,
  actions: ActionsTrss<T, T>
}

function createStore<S>(storeOptions: StoreOptions<S>) {
  return new Store<S>(storeOptions)
}

let storeOptions: StoreOptions<string> = {
  state: 'kk',
  actions: {
    add({ commit }, payload) {
      commit('food', '宫傲鸡丁')
    },
    del({ commit, state }) {

    }
  }
}
相关推荐
PAK向日葵29 分钟前
【算法导论】PDD 0817笔试题题解
算法·面试
加班是不可能的,除非双倍日工资3 小时前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi3 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip4 小时前
vite和webpack打包结构控制
前端·javascript
excel4 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国4 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼4 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jimmy4 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
ZXT5 小时前
promise & async await总结
前端
Jerry说前后端5 小时前
RecyclerView 性能优化:从原理到实践的深度优化方案
android·前端·性能优化