在本文中,我们将了解到 tuple 类型的一些特殊操作,以及如何使用 infer 来进行推断,与递归操作结合,最终解决问题。

Tuple to Object

在本题中,我们将实现将一个数组转化为一个对象,这个对象的 key 与 value 必须是相同的,且 key\value 在该数组中

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as consttype result = TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}

测试用例

import type { Equal, Expect } from '@type-challenges/utils'const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
const tupleNumber = [1, 2, 3, 4] as const
const tupleMix = [1, '2', 3, '4'] as consttype cases = [Expect<Equal<TupleToObject<typeof tuple>, { tesla: 'tesla'; 'model 3': 'model 3'; 'model X': 'model X'; 'model Y': 'model Y' }>>,Expect<Equal<TupleToObject<typeof tupleNumber>, { 1: 1; 2: 2; 3: 3; 4: 4 }>>,Expect<Equal<TupleToObject<typeof tupleMix>, { 1: 1; '2': '2'; 3: 3; '4': '4' }>>,
]// @ts-expect-error
type error = TupleToObject<[[1, 2], {}]>

根据题意与测试用例,我们可以提取出以下几点

1.需要传入一个 tuple 类型,且 tuple 中的类型不能是数组与对象
2.需要对传入的 T 进行遍历
3.返回一个对象,对象的 key/value 必须是相同的

相信通过前两篇文章的阅读,我们已经对这些条件相当熟悉了,所以我们就不在先通过 JS 来实现这个题目,直接使用 TS 来进行编写。

对传入的数据进行判断

在编写相关代码之前,我们先对测试用例中出现的知识点进行分析。在测试用例中,我们声明了一些数组并用 as const 将它们转化为字面量类型(literal type),在这之后再使用 typeof 将这些数组转化为 tuple。

当我们在使用 const 对变量进行声明时,如 const world = “world” 时,TS 会自动将 world 的类型转化为字面量类型 world。同理,我们使用 as const 对 tupleNumber 数组进行类型断言,那么该数组的类型会从 number[] 转化为 tuple,它的值为 [1,2,3,4]。

再回到 typeof 操作符,我们要求在传入参数时,传入一个类型,而不是一个数组,在这时候 typeof 的作用就体现出来了。形象地来说,typeof 就像是连接 JS 世界与 TS 世界的一个通道。当我们想要在 TS 世界使用 JS 世界内的数据时,我们可以使用 typeof 来将 JS 世界内的数据映射到 TS 中。

通过以上解释,我们就不难理解为什么测试用例中要对数据进行这两步处理,接下来,我们来对我们的题目进行编写

type TupleToObject<T extends readonly PropertyKey[]> = {}

在这段代码中,我们利用 Propertykey 以及 readonly 来对 T 传入的类型进行判断。因为传入的 T 是以字面量类型组成的 tuple,我们不可以对其进行修改,所以使用 readonly 对其进行限制。

我们之后再使用 PropertyKey 来对 tuple 中的类型进行限制。Propertykey 的默认值是一个 union,这个 union 的默认值为 string | number | symbol。 这样我们就完成了第一个点

对传入的 T 进行遍历,对象的 key/value 必须是相同的

因为后两个点其实是两两关联的,所以合作一点说明

type TupleToObject<T extends readonly PropertyKey[]> = {[P in T[number]]: P
}

在使用 in 对 T 进行遍历的时候,P 可以看作 T 中的每一项值,所以我们只要使用 P 来作为 value 就可以完成第三点。

值得注意的是,in 操作符右边需要传入一个 union。对于对象,我们可以使用 keyof 来对对象进行操作,他会返回一个以对象的属性组成的 union。而我们这次传入的是一个 tuple,所以我们这次使用 T[number] 来将数组内的数据取出并转化为 union。

至此这道题目已经完整解出。

Tuple to Union

在这道题中,我们将实现一个将 tuple 转换为 union 的泛型 TupleToUnion

type Arr = ['1', '2', '3']type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'

测试用例

import type { Equal, Expect } from '@type-challenges/utils'type cases = [Expect<Equal<TupleToUnion<[123, '456', true]>, 123 | '456' | true>>,Expect<Equal<TupleToUnion<[123]>, 123>>,
]

在这道题中,我将提供两种解法。

  • 解法一:利用 tuple 本身的特性来解决
  • 解法二:利用递归与 infer 来进行解答

相信如果阅读了上一道题的解答过程,我们可以很容易的想到可以利用 T[number] 来将一个 tuple 转化为 union。

type TupleToUnion<T extends any[]> = T[number]

下面我们来着重讲解第二种解答,利用递归与 infer 来进行解答。先给出答案,之后再一步步分析解题过程

type TupleToUnion<T> = T extends [infer First, ...infer Rest] ? First | TupleToUnion<Rest>: never

在开始分析时,我们不妨先搞明白 infer 它到底是干什么的。在官方文档中,他用一句话

Within the extends clause of a conditional type, it is now possible to have infer declarations that introduce a type variable to be inferred.

翻译过来就是,在条件类型的子句中,现在可以使用 infer 声明来引入要推断的类型变量。

也就是说,我们可以在 extends 后面使用 infer 来对类型变量进行声明引入。是不是听起来十分的拗口,我们不妨使用数学中设置未知变量的思维来理解他。在数学中,当我们不知道某个变量的具体值时,我们可以设置一个未知变量 X 来指代他,方便我们进行下一步操作。所以 infer 的作用也是正是如此,我们可以设置一个未知的类型变量来进行下一步操作。

在本题中我们使用 [infer First, …infer Rest] 来对 T 中的类型进行假设,其中利用了 rest parameter 来对数组的剩余参数进行合并。如果当前的 T 满足这个推断则进行 First | TupleToUnion 这个分支,再将 Rest 参数传入进行递归调用,最终得到结果。

至此,此题已经解决。如果对 infer 还有疑惑,我会将 TS 官方文档对 infer 的解释的链接放在最后,看完官方文档及示例后,相信你会完全理解 infer。

Tuple to Nested Object

在本题中,传入一个只包含字符串类型的元组 T,以及一个类型 U,以递归的方式构建一个对象

type a = TupleToNestedObject<['a'], string> // {a: string}
type b = TupleToNestedObject<['a', 'b'], number> // {a: {b: number}}
type c = TupleToNestedObject<[], boolean> // boolean. if the tuple is empty, just return the U type

测试用例

import type { Equal, Expect } from '@type-challenges/utils'type cases = [Expect<Equal<TupleToNestedObject<['a'], string>, { a: string }>>,Expect<Equal<TupleToNestedObject<['a', 'b'], number>, { a: { b: number } }>>,Expect<Equal<TupleToNestedObject<['a', 'b', 'c'], boolean>, { a: { b: { c: boolean } } }>>,Expect<Equal<TupleToNestedObject<[], boolean>, boolean>>,
]

乍一看我们好像并没有什么思路,那么我们不妨先使用 JS 的思维进行解答

function tuple(arr,type) {const [first, ...rest] = arrif (arr.length === 0) {return type}return {[first]: tuple(rest,type)}
}

当我们使用 JS 进行递归解答,我们可以很容易的得出结果。从中我们再对其进行要点提取

1.对传入的 T 进行解构
2.返回一个对象,该对象的 key 为解构出的第一个值,value 为递归调用的函数,同时将剩余参数 rest 传入
3.当传入的 T 的不满足推断的格式时,结束递归,返回 U

下面我们来一步步实现整个题目

对类型进行解构,当 T 不满足时返回 U

经过前面两道题的练习,很容易的就可以想到用 [infer First, …infer Rest] 来对 T 进行解构,或者说是推断 T 中的类型,而 infer 通常是与 extends 结合使用来进行判断

type TupleToNestedObject<T, U> = T extends [infer F, ...infer R] ? {}: U

值得注意的是,在对 T 进行 extends 操作时,我们进行的是整个判断,所以当递归的 T 中的值不满足时,也就是只有一个值,无法解构出 rest 参数时,进入第二个分支,返回 U

返回一个对象进行递归操作

在上一步骤中,我们已经实现了返回一个对象。下面我们根据已经实现的 JS 代码可以容易的写出剩余部分

type TupleToNestedObject<T, U> = T extends [infer F extends PropertyKey, ...infer R] ? {[P in F] : TupleToNestedObject<R, U>
} : U

值得注意的是,我们在对象中要使一个 union 变为 key 时,需要利用 in 操作符。而在对象中,key 必须是 Properkey 类型,所以需要添加一个约束来限制 F 的类型。

至此,所有题型已完整解出。

最后

为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

一款纯 JS 实现的轻量化图片编辑器相关推荐

  1. Three.js BIM模型轻量化 FPS渲染速率优化 多 实例渲染[Instance]+顶点合并[Merge]

    BIM模型不经过处理,直接加载到Three.js 创建的场景中,很大可能会很使fps帧率下降,原因在于模型的个数太多,有的模型是多材质的话还需要在Three.js中绘制两次,这样会导致drawcall ...

  2. 又发现一款纯js开源电子表格Luckysheet

    据官网介绍这个电子表格插件,是一款纯前端类似excel的在线表格,功能强大.配置简单.完全开源. 官网链接: Luckysheet官网 在线DEMO 特性包含: 表格设置,包括冻结行列.合并单元格.筛 ...

  3. 奇瑞小蚂蚁,国内轻量化的“标杆”

    目前轻量化已成为新能源汽车产业达成节能减排目标的重要途径,奇瑞新能源作为国内最早研发新能源汽车的企业之一,在轻量化技术领域拥有着深厚经验,奇瑞小蚂蚁作为轻量化的"标杆",目前是自主 ...

  4. html5 下拉框 美化,纯js超酷select下拉框美化插件

    tastySelect是一款纯js超酷select下拉框美化插件.tastySelect下拉框插件支持多选,内置两种主题,使用CSS3动画过渡效果,整体设计时尚大方. 使用方法 在页面中引入tasty ...

  5. 倾斜摄影超大场景的三维模型轻量化与三维展示效果的关系浅析

    倾斜摄影超大场景的三维模型轻量化与三维展示效果的关系浅析 倾斜摄影超大场景的三维模型由于数据量庞大,直接进行渲染可能会导致计算资源和时间的浪费.因此,针对倾斜摄影超大场景的三维模型区域进行轻量化处理是 ...

  6. 浅析提高倾斜摄影超大场景的三维模型轻量化的数据质量关键技术

    浅析提高倾斜摄影超大场景的三维模型轻量化的数据质量关键技术 倾斜摄影超大场景的三维模型轻量化的质量关键技术主要包括: 1.保持数据精度.在进行轻量化处理时,必须确保数据的精度不受损失,否则会影响后续分 ...

  7. html图片百叶窗轮播,纯js百叶窗效果轮播图插件

    这是一款纯js百叶窗效果轮播图插件.该js轮播图在图片切换时的效果类似于多张图片展开合成一张完整的大图.该轮播图由纯js编写,兼容ie8浏览器. 使用方法 在页面中引入imgSwitch.min.js ...

  8. html5图片自动翻转,纯js实现360度旋转预览图片特效

    这是一款纯js实现360度旋转预览图片特效.该js特效仅使用120行代码,即可实现通过滑块.或鼠标手动360度旋转图片,以及自动360度旋转图片的效果. 使用方法 HTML结构 import Roll ...

  9. html js 循环提示框,纯js超酷消息提示框插件notice.js

    notice.js是一款纯js超酷消息提示框插件.notice.js为纯js编写,没有任何依赖文件.通过它可以在页面上制作出漂亮的toast消息通知框效果.该js消息提示框插件的特点还有: 支持4中情 ...

最新文章

  1. 微信小程序获取用户收货地址 完整代码
  2. 【LVS】负载均衡集群
  3. C# WPF 表单更改提示
  4. JavaScript-表单提交验证及前端密码MD5加密
  5. 【转载】WebService到底是什么?
  6. P2P终结者 操作用法(如何限速)
  7. 【JavaWeb】【笔记】《JavaWeb入门经典》 第15章 Struts框架
  8. 苹果笔记本适合什么人 中国Mac电脑用户的8个事实
  9. 各种带有物理学特点的把妹法[转]
  10. unity学习笔记(二)—— 制作第一个小游戏
  11. 浅谈Ambarella 的BOSS架构
  12. 团队springboot基础镜像选择思考
  13. 常用的前端JavaScript方法封装
  14. Python如何设置文件保存位置(txt文件保存位置)
  15. 微信小程序记录与项目实践
  16. 孤独的灵魂 - 复旦投毒案
  17. 微信微博抖音web授权登录
  18. C语言检测是否加入一个QQ群,判断登录的QQ是否已经加入指定的QQ群
  19. python QRcode
  20. nRF52832使用spi或者twi出现静态400-450ua电流的问题,由GPIOTE引起

热门文章

  1. SCI期刊收不收费也有门道,你知道吗?
  2. Cadence的启动路径
  3. 跨域 -- cors
  4. fastboot工具linux,Ubuntu 14.10/14.04 用户如何安装快速启动工具 Mutate 2.2
  5. 安装envi5.3时找不到证书license
  6. 安卓模拟器玩绝地求生:刺激战场电脑版怎么避免模拟器检测教程
  7. 广告公司内部流出版:互联网广告作弊的十八般武艺
  8. 标准工时最常用的计算方法有哪些?什么方法最好用呢?
  9. 图像序列帧恢复为GIF动图
  10. 计算机通信辞典登录,bit