简单三行代码函数实现几十行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类型推导代码函数的资料请关注我们其它相关文章!

(0)

相关推荐

  • TypeScript 基本数据类型实例详解

    目录 TypeScript 介绍 类型分配 类型推导 数组 元组 object null 和 undefined 特殊类型 any unknown never void TypeScript 介绍 TypeScript 是 JavaScript 的超集,提供了 JavaScript 的所有功能,并提供了可选的静态类型.Mixin.类.接口和泛型等特性. TypeScript 的目标是通过其类型系统帮助及早发现错误并提高 JavaScript 开发效率. 通过 TypeScript 编译器或 Ba

  • TypeScript类型级别和值级别示例详解

    目录 对值级别编程类型级别编程区分 类型级编程 挑战是如何工作的 挑战 对值级别编程类型级别编程区分 首先,让我们对值级别编程和类型级别编程进行重要区分. 值级别编程让我们编写将在生产中运行的代码即运行期,并为我们的用户提供有用的东西. 类型级别编程帮助我们确保代码在发布之前即编译期不包含错误,在运行期会被完全删除 JavaScript没有类型,所以所有JavaScript都是值级别的代码: // A simple Javascript function: function sum(a, b)

  • TypeScript 类型级别示例介绍

    介绍 这是一门在线课程,旨在将您的TypeScript技能从中级提升到高级.它将使你深入了解类型系统的基本原理,并指导你完成其高级功能.在这里,你会找到成为TypeScript专家所需的一切-不仅有深入的内容,还有练习新技能的有趣挑战,就像这里的这个. /** * Try assigning "World" to `type Hello`! */ type Hello = "..."; // Type-level unit tests! // If the next

  • typescript基本数据类型HTMLElement与Element区别

    目录 TypeScript是什么? TypeScript的安装和编译 上手实践 typescript中HTMLElement 和 Element的区别 探讨 TypeScript是什么? 涉及代码仓库 github.com/Dartgm/dart… TypeScript是由微软开发的一款开源的编程工具 TypeScript是JavaScript的超集,遵循最新的ES5/ES6规范,TypeScript扩展了JavaScript的语法 TypeScript更像后端Java,C#这样的面向对象的语言

  • TypeScript 背后的结构化类型系统原理详解

    目录 前言 什么是结构化类型系统? 什么是标称类型系统? 结构化类型系统等价于鸭子类型系统吗? 如何在 TypeScript 中模拟标称类型系统? 交叉类型实现 类实现 总结 前言 你能说清楚类型.类型系统.类型检查这三个的区别吗?在理解TypeScript的结构化类型系统之前,我们首先要搞清楚这三个概念和它们之间的关系 类型:即对变量的访问限制与赋值限制.如 TypeScript 中的原始类型.对象类型.函数类型和字面量类型等类型,当一个变量类型确定后,你不能访问这个类型中不存在的属性或方法,

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

    目录 场景 摸鱼吃瓜式排查 元组大法 感叹 场景 最近在设计一些基础的项目框架设计上的 sdk api,比如埋点系统.权限系统之类的,要提供一些便捷的封装方法给上层使用.于是遇到了这么个场景. 有一个对象常量里,存了一些方法,例如: const METHODS = { a: () => "a" as const, b: () => "b" as const, c: () => "c" as const } 然后想要封装这样一个

  • PowerShell中简单的自定义函数和调用函数例子

    在PowerShell中是否有函数?PowerShell是否可以自定义函数?PowerShell中如何自定义函数? 在PowerShell中,我们可以使用函数(function)来简化编程开发.在PowerShell中使用function这个关键词来标识一个函数.一个自定义的函数,由function关键词开始,然后用一对大括号来包括起整个函数体的内容. 我们先来看一下简单的PowerShell函数: 复制代码 代码如下: function Test-Function {     Write-Ho

  • Python单链表简单实现代码

    本文实例讲述了Python单链表简单实现代码.分享给大家供大家参考,具体如下: 用Python模拟一下单链表,比较简单,初学者可以参考参考 #coding:utf-8 class Node(object): def __init__(self, data): self.data = data self.next = None class NodeList(object): def __init__(self, node): self.head = node self.head.next = No

  • Python多进程同步简单实现代码

    本文讲述了Python多进程同步简单实现代码.分享给大家供大家参考,具体如下: #encoding=utf8 from multiprocessing import Process, Lock def func(lock, a): lock.acquire() print a lock.release() if __name__ == '__main__': lock = Lock() workers = [] # 创建两个进程 for i in range(0, 2): p = Process

  • window.open不被拦截的简单实现代码(推荐)

    实例如下: $("#last").click(function(){ var w=window.open(); setTimeout(function(){ w.location="http://www.jb51.net"; }, 1000); return false; }); window.open是javascript函数,该函数的作用是打开一个新窗口或这改变原来的窗口,如果你直接在js中调用window.open()函数去打开一个新窗口,浏览器会拦截你,那么

  • 关于jQuery里prev()的简单操作代码

    prev() 函数被用来匹配元素集的前一个兄弟元素,仅仅只有前一个兄弟元素被选择,其子元素将被忽略.这里给大家介绍jQuery里prev()的简单操作,具体内容如下: 一个朋友提了个需求给我:点击按钮删除input,如果input剩一个,点击按钮全部消失. 很简单的操作,但是你如果不知道jquery里有prev()这个方法,可能你会走很多弯路. 代码: html <div> <input type="text" placeholder="用户名"&

  • 无缝滚动的简单实现代码(推荐)

    原理: 1.给ul一个绝对定位使其脱离文档流,left设置为0,把图片塞进ul里,编写一个"移动"函数,函数功能能够使ul的left以一个正速度向右跑动, 2.设置一个定时器,让"移动"函数每30(参数可变)毫秒执行一次 3.因为ul的长度会"跑"完,此时可以使ul的content也就是img增加一倍, oUl.innerHTML +=oUl.innerHTML; 4.此时因为ul的content增加,其width也会随着增大,根据实际项目展示图

  • Python 用三行代码提取PDF表格数据

    从 PDF 表格中获取数据是一项痛苦的工作.不久前,一位开发者提供了一个名为 Camelot 的工具,使用三行代码就能从 PDF 文件中提取表格数据. PDF 文件是一种非常常用的文件格式,通常用于正式的电子版文件.它能够很好的将不同的排版格式固定下来,形成版面清晰且美观的展示效果.然而,对于想要从 PDF 中提取信息的人们来说,PDF 是个噩梦,尤其是表格. 大量的学术报告.论文.分析文章都使用 PDF 展示其中的表格数据,但是对于如果想要直接从表格中复制数据则会非常麻烦.不久前,有一位开发者

  • Python基于opencv的简单图像轮廓形状识别(全网最简单最少代码)

    可以直接跳到最后整体代码看一看是不是很少的代码!!!! 思路: 1. 数据的整合 2. 图片的灰度转化 3. 图片的二值转化 4. 图片的轮廓识别 5. 得到图片的顶点数 6. 依据顶点数判断图像形状 一.原数据的展示 图片文件共36个文件夹,每个文件夹有100张图片,共3600张图片. 每一个文件夹里都有形同此类的图形 二.数据的整合 对于多个文件夹,分析起来很不方便,所有决定将其都放在一个文件夹下进行分析,在python中具体实现如下: 本次需要的包 import cv2 import os

  • 提取视频中的音频 Python只需要三行代码!

    身处数据爆炸增长的信息时代,各种各样的数据都飞速增长,视频数据也不例外.我们可以使用 python 来提取视频中的音频,而这仅仅需要安装一个体量很小的 python 库,然后执行三行代码! 语音数据在数据分析领域极为重要.比如可以分析语义.口音.根据人的情绪等等.可以应用于偏好分析.谎话检测等等. 一.提取音频 需要用到 python 的 moviepy 库 moviepy的 github 地址:https://github.com/Zulko/moviepy 命令行 pip 安装上 movie

随机推荐