详解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 }) {

    }
  }
}
相关推荐
哑巴语天雨18 分钟前
React+Vite项目框架
前端·react.js·前端框架
初遇你时动了情31 分钟前
react 项目打包二级目 使用BrowserRouter 解决页面刷新404 找不到路由
前端·javascript·react.js
乔峰不是张无忌3301 小时前
【HTML】动态闪烁圣诞树+雪花+音效
前端·javascript·html·圣诞树
Wyang_XXX1 小时前
CSS 选择器和优先级权重计算这么简单,你还没掌握?一篇文章让你轻松通关面试!(下)
面试
鸿蒙自习室1 小时前
鸿蒙UI开发——组件滤镜效果
开发语言·前端·javascript
m0_748250741 小时前
高性能Web网关:OpenResty 基础讲解
前端·openresty
前端没钱2 小时前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js
NoneCoder2 小时前
CSS系列(29)-- Scroll Snap详解
前端·css
无言非影2 小时前
vtie项目中使用到了TailwindCSS,如何打包成一个单独的CSS文件(优化、压缩)
前端·css
我曾经是个程序员2 小时前
鸿蒙学习记录
开发语言·前端·javascript