TypeScript 类型推导还可以这么用?

13 阅读3分钟

一、为什么需要TypeScript类型推导

肯定是为了节省我们的代码,减少冗余,下面举一个例子,写一个递减函数

下面代码中,写了三个类型都是number,那么我们是不是可以思考,如何减少冗余呢,ts给咱们提供了泛型

正常书写

const desc = (a: number): number => {
    return --a
}
desc(2) // 1

泛型

const desc = <T>(a: T): number => {
    return --a
}
desc(3) // 2

这个例子在项目中的应用场景可谓之少之又少,也可以说,压根不可能出现。
那么衍生出一个问题:有没有更好的方法,自动推导我特定的入参返回特定类型。

有的兄弟,有的!!

我先把公共代码抽出来

// 公共属性
type Pet = {
    name: string
    age: number
}

// 映射类型
type OptionsMap = {
    dog?: {
        bark: string
        barkVolume: number
    } & Pet

    cat?: {
        favoriteToy: string
        furLength: 'short' | 'medium' | 'long'
    } & Pet
}

场景:

  • 组件封装,表单、表单组件类型封装,例如通过表单项type来确定是input、select...就可以明确props类型了
  • 业务逻辑封装,在重复的业务逻辑中我们通常会抽离,通过key去调用,那么对应的参数不同,也会用到泛型、自动推导

二、映射 + 类型推导

我们可以通过映射、类型推导相互配合就能得到特定的类型了,废话少说,上代码

2.1. 先来一个函数案例,通过参数1推导出参数2


const fn = <T extends keyof OptionsMap>(type: T, props: OptionsMap[T]) => {}

fn('cat', {}) 
/*
类型“{}”的参数不能赋给类型“{ favoriteToy: string; furLength: "short" | "medium" | "long"; } & Pet”的参数。  
类型“{}”缺少类型“{ favoriteToy: string; furLength: "short" | "medium" | "long"; }”中的以下属性: favoriteToy, furLengthts-plugin(2345)
*/ 

  • 上面代码声明了宠物映射属性,通过函数的第一个参数去映射中自动匹配第二个参数
  • 恭喜你现在已经掌握类型推导的基础了

2.2. 下面我们再来一个写一个生成集合,里面数据项都是通过推导出来的

type SinglePet<T extends keyof OptionsMap> = {
    type: keyof OptionsMap
    options: OptionsMap[T]
}

type Pets = SinglePet<keyof OptionsMap>[]

const petHome: Pets = [
    {
        type: 'dog',
        options: {
            name: '旺财',
            age: 2,
            bark: '汪汪汪',
            barkVolume: 2,
        },
    },
    {
        type: 'cat',
        options: {
            name: '小白',
            age: 1,
            favoriteToy: '玩具鸟',
            furLength: 'short',
        },
    },
]

数组里的每一项options,我们都是通过type推导出来的。


三、extends + 类型推导

extends和映射作用都是为了自动推导出另一个属性或者另一个类型,作用是一样的,具体使用哪种,可以根据项目而定


type PetKeys = 'dog' | 'cat' | 'fish' | 'bird'

type PetOptions<T extends PetKeys> = T extends 'dog'
    ? OptionsMap['dog']
    : T extends 'cat'
      ? OptionsMap['cat']
      : {
            text: number
        }

type OptionItem<T extends PetKeys> = PetOptions<T>

type Item<T extends PetKeys = PetKeys> = {
    type: T
    options: OptionItem<T>
}

type PetHome = Item[]

const petHome: PetHome = [
    {
        type: 'dog',
        options: {
            name: '旺财',
            age: 2,
            bark: '汪汪汪',
            barkVolume: 10,
        },
    },
    {
        type: 'bird',
        options: {
            text: 1,
        },
    },
    {
        type: 'cat',
        options: {
            name: '小白',
            age: 2,
            favoriteToy: '玩具鸟',
            barkVolume: 5,
            furLength: 'long',
        },
    },
]

上面这段代码主要是使用extend判断对应的类型,在项目中封装很常见,也利于后期拓展,其中核心代码为

type Item<T extends PetKeys = PetKeys> = { 
    type: T options: OptionItem<T> 
}

四、总结

类型推导可以不用,不能不会,有ts的项目必出现这种场景,当然也可以用AI 😂

最后告诉大家几个泛型、推导的注意事项

泛型参数,也就是本文中的T,如果一个使用项中的T已经明确类型了,其他地方也会跟着明确。
泛型参数一旦确定就不会再变,所有咱们如果使用默认值时,引用的类型必须要传递,否则就是按默认值算了。

最后祝大家日入过万,给俺点点关注、点点赞