简单三行代码函数实现几十行Typescript类型推导

 更新时间:2023年01月15日 16:41:46   作者:xekin  
这篇文章主要为大家介绍了简单三行代码函数实现几十行Typescript类型推导的方案示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

场景

最近在设计一些基础的项目框架设计上的 sdk api,比如埋点系统、权限系统之类的,要提供一些便捷的封装方法给上层使用。于是遇到了这么个场景。

有一个对象常量里,存了一些方法,例如:

    const METHODS = {
        a: () => "a" as const,
        b: () => "b" as const,
        c: () => "c" as const
    }

然后想要封装这样一个 hook 例如 useMethod 给上层的 React 上下文使用:

    type MethodKey = keyof typeof METHODS
    function useMethods(keys: MethodKey[]) {
        return keys.map(key => METHODS[key])
    }
    // case
    const [a, b] = useMethods(['a', 'b'])
    // expect to "a"
    a();

一切都简简单单,属于是日常到不能再日常的代码,可是当我在 IDE 里挪上去一看,这不对劲呀:

我预期这里应该类型直接就是字符串 a 了,怎么会是个联合类型?

摸鱼吃瓜式排查

我上上下下看了一遍类型推导,发现 keys.map(key => METHODS[key]) 这一句里,key 直接被推导成了 "a" | "b" | "c"

所以理所当然的结果也是推导成了 "a" | "b" | "c"

emmm......这还有些麻烦,先单独写个类型方法来推导结果试试,递归传入的数组泛型,取出每一次的 key 对应的 method,再组合为数组。

 type MethodValue<K extends MethodKey> = typeof METHODS[K]
 type GetMethodValue<T extends MethodKey[]> = T extends [] 
     ? [] 
     : T extends [infer F extends MethodKey, ...infer Rest extends MethodKey[]]
     ? [MethodValue<F>, ...GetMethodValue<Rest>]
     : never 

测试一下:

再将类型回到方法 useMethod 上带入却发现完全不行:

如果强行断言 map 返回的结果,则直接会被推导为 never 类型

元组大法

其实不难从代码里看出,之所以无法推导原因有两点,第一点是在 Typescript 编译时这个阶段,是无法推导这个函数泛型传参的多种形态中的 key 是怎样排序的,其次是在 map 方法中,key 值一直被推导成 "a" | "b" | "c" 导致。

所以如果我用元组作为泛型限定值,倒是可以实现:

type GetMethodValue<T extends (MethodKey | void)[]> = T extends []
     ? []
     : T extends [infer F extends MethodKey, ...infer Rest extends MethodKey[]] 
     ? [MethodValue<F>, ...GetMethodValue<Rest>] 
     : never 
function useMethods<T extends ['a'?, 'b'?, 'c'?]>(keys: T) {
    return keys.filter((key): key is MethodKey => !!key).map((key) => METHODS[key]) as GetMethodValue<T>
}
const [a, b] = useMethods(['a', 'b'])
const valueA = a()

理解到这,我就思考虽然类型不能自动推导出元组的组合排列方式,但是我却可以写一个方法来实现推导联合类型生成元组。

    type Permutation<T, U = T> = [T] extends [never]
      ? []
      : U extends T
      ? [U, ...Permutation<Exclude<T, U>>]
      : never;
    // expect to ['a', 'b'] | ['b', 'a']
    type value = Permutation<'a' | 'b'>

这是我之前在写 TypeChallenge 时写过的方法,这就派上用场了。

直接将 MethodKey 这个联合类型解成元组之后限定泛型 T,最后确实也可以成功推导结果。

type Permutation<T, U = T> = [T] extends [never]
  ? []
  : U extends T
  ? [U?, ...Permutation<Exclude<T, U>>]
  : never;
type MethodKey = keyof typeof METHODS
type MethodValue<K extends MethodKey> = typeof METHODS[K]
type GetMethodValue<T extends (MethodKey | void)[]> = T extends []
     ? []
     : T extends [infer F extends MethodKey, ...infer Rest extends MethodKey[]] 
     ? [MethodValue<F>, ...GetMethodValue<Rest>] 
     : never 
const METHODS = {
    a: () => "a" as const,
    b: () => "b" as const,
    c: () => "c" as const
}
function useMethods<T extends Permutation<MethodKey>>(keys: T) {
    return keys.filter((key): key is MethodKey => !!key)
               .map((key) => METHODS[key]) as GetMethodValue<T>
}
const [a, b] = useMethods(['a', 'b'])
const valueA = a()

感叹

只是一个三行代码就实现的简单方法,但要做出准确的结果推导却需要这么复杂的类型声明去铺垫,虽然最后写出来很爽,但也感叹作为库开发者的一方真是非常不容易,这当中为了类型推导,还增加了冗余的代码,为了支持元组的可选值,不得不将变量打为可选,从而需要先 filtermap 才能保证结果不会出现空值的类型推导。

身为一个前端,在写 Ts 时时不时就要为几个简单结果的推导准确性花上小半天时间,有时候也觉得很不值得,不知道其他语言在类型上是否也有类似的烦恼,也希望 Typescript 团队能有更好的类型推断手段演进。

本文最后的解决方案不一定为最佳解决方案,不过作者也在社区和搜索网站上检索过答案,最后也没找到满意的解答,更多关于Typescript类型推导代码函数的资料请关注脚本之家其它相关文章!

相关文章

  • typescript难学吗?前端有必要学?该怎么学typescript

    typescript难学吗?前端有必要学?该怎么学typescript

    TypeScript代码与 JavaScript 代码有非常高的兼容性,无门槛,你把 JS 代码改为 TS 就可以运行。TypeScript 应该不会脱离 JavaScript 成为独立的语言。学习 TypeScript 应该主要指的是学习它的类型系统。
    2022-12-12
  • TypeScript类型any never void和unknown使用场景区别

    TypeScript类型any never void和unknown使用场景区别

    这篇文章主要为大家介绍了TypeScript类型any never void和unknown使用场景区别,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • typescript快速上手的进阶类型与技术

    typescript快速上手的进阶类型与技术

    本文讲述了typescript开发的一些高级的类型与技术,算是对于基础知识点的补充,具体内容包括:比如元组、枚举类、接口、泛型相关概念等。虽说是进阶,但是内容不算多也并不难理解。
    2022-12-12
  • type-challenge刷题(easy部分)示例详解

    type-challenge刷题(easy部分)示例详解

    这篇文章主要为大家介绍了type-challenge刷题(easy部分)示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • postman数据加解密实现APP登入接口模拟请求

    postman数据加解密实现APP登入接口模拟请求

    对于Postman的使用,一般情况下只要发发确定的请求与参数就可以的了,然而,在使用的时候,尤其是接口测试时,请求接口的设计里面都有数据加密,参数验签,返回数据也有进行加密的,这个时候就需要使用一些脚本做处理,模拟app登入请求的操作
    2021-08-08
  • ts 类型体操 Chainable Options 可链式选项示例详解

    ts 类型体操 Chainable Options 可链式选项示例详解

    这篇文章主要为大家介绍了ts 类型体操 Chainable Options 可链式选项示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • TypeScript十大排序算法插入排序实现示例详解

    TypeScript十大排序算法插入排序实现示例详解

    这篇文章主要为大家介绍了TypeScript十大排序算法插入排序实现示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • TypeScript手写一个简单的eslint插件实例

    TypeScript手写一个简单的eslint插件实例

    这篇文章主要为大家介绍了TypeScript手写一个简单的eslint插件实例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • typescript快速上手的基础知识篇

    typescript快速上手的基础知识篇

    静态类型的typescript与传统动态弱类型语言javascript不同,在执行前会先编译成javascript,因为它强大的type类型系统加持,能让我们在编写代码时增加更多严谨的限制。注意,它并不是一门全新的语言,所以并没有增加额外的学习成本
    2022-12-12
  • TypeScript 交叉类型使用方法示例总结

    TypeScript 交叉类型使用方法示例总结

    这篇文章主要为大家介绍了TypeScript 交叉类型使用方法示例总结,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08

最新评论