TS 通过类型也能实现 Array.includes?

题目

在类型系统里实现 JavaScript 的 Array.includes 方法,这个类型接受两个参数,返回的类型要么是 true 要么是 false

例如:

ts 复制代码
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> 
// expected to be `false`

测试 Case:

ts 复制代码
type cases = [
  Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'>, true>>,
  Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'>, false>>,
  Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 7>, true>>,
  Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 4>, false>>,
  Expect<Equal<Includes<[1, 2, 3], 2>, true>>,
  Expect<Equal<Includes<[1, 2, 3], 1>, true>>,
  Expect<Equal<Includes<[{}], { a: 'A' }>, false>>,
  Expect<Equal<Includes<[boolean, 2, 3, 5, 6, 7], false>, false>>,
  Expect<Equal<Includes<[true, 2, 3, 5, 6, 7], boolean>, false>>,
  Expect<Equal<Includes<[false, 2, 3, 5, 6, 7], false>, true>>,
  Expect<Equal<Includes<[{ a: 'A' }], { readonly a: 'A' }>, false>>,
  Expect<Equal<Includes<[{ readonly a: 'A' }], { a: 'A' }>, false>>,
  Expect<Equal<Includes<[1], 1 | 2>, false>>,
  Expect<Equal<Includes<[1 | 2], 1>, false>>,
  Expect<Equal<Includes<[null], undefined>, false>>,
  Expect<Equal<Includes<[undefined], null>, false>>,
]

初始代码:

ts 复制代码
type Includes<T extends readonly any[], U> = any

题解

我第一反应想到的,可以通过条件类型来判断 U 是否可以赋值给 T 中的元素。通过 T[number] 来获取 T 中所有元素的联合类型:

ts 复制代码
type Includes<T extends readonly any[], U> = U extends T[number] ? true : false;

不过这样错了,比如下面的例子:

ts 复制代码
Expect<Equal<Includes<[{}], { a: 'A' }>, false>>,

虽然 { a: 'A' } 可以赋值给 {},但是不代表两者相等。

现在,我们需要一个方案来判断两个类型是否相等,虽然这有点难,不过早已经有前辈写好了:

ts 复制代码
export type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
  ? true
  : false

原理有点复杂,准备后面单独写一篇文章来介绍。总计记住就好了。

接下来就可以实现 Includes

ts 复制代码
type Includes<T extends readonly unknown[], U> = T extends [infer First, ...infer Rest]
  ? Equal<First, U> extends true ? true : Includes<Rest, U>
  : false;

我们先判断第一个元素是否为查找类型,然后再判断剩余的数组中是否包含该类型,递归解决问题。

这里面涉及的知识点前面有讲到过,这里再复习一下:

  • T extends readonly unknown[] 类型约束, T 被限制为一个只读数组或元组,其元素类型是 unknown
  • T extends [infer First, ...infer Rest]:这是一个条件类型,它检查 T 是否可以赋值给一个元组类型,其中第一个元素被推断为 First,剩余的元素被推断为 Rest。如果 T 是空数组或空元组,这个条件将不成立。
  • infer First...infer Restinfer 关键字在这里用来在条件类型的上下文中推断元组的第一个元素的类型(First)和剩余元素的数组类型(Rest
  • Equal<First, U> extends true ? true : Includes<Rest, U>:如果 T 可以赋值给 [infer First, ...infer Rest],则进一步检查 First 元素是否与类型 U 相等。这通过另一个假设存在的类型别名 Equal 来完成,它应该能够检查两个类型是否相等。如果 FirstU 相等,结果是 true。如果不相等,递归地调用 Includes<Rest, U> 来检查剩余的元素。
  • : false:如果 T 不是一个非空元组或数组,或者 FirstU 不相等且 Rest 为空数组,则 Includes 类型解析为 false

这样,我们就可以通过 Includes 来判断一个类型是否包含在某个元祖类型中了。

相关推荐
小墨宝5 分钟前
js 生成pdf 并上传文件
前端·javascript·pdf
HED20 分钟前
用扣子快速手撸人生中第一个AI智能应用!
前端·人工智能
DN金猿24 分钟前
使用npm install或cnpm install报错解决
前端·npm·node.js
丘山子25 分钟前
一些鲜为人知的 IP 地址怪异写法
前端·后端·tcp/ip
志存高远6638 分钟前
Kotlin 的 suspend 关键字
前端
www_pp_1 小时前
# 构建词汇表:自然语言处理中的关键步骤
前端·javascript·自然语言处理·easyui
YuShiYue1 小时前
pnpm monoreop 打包时 node_modules 内部包 typescript 不能推导出类型报错
javascript·vue.js·typescript·pnpm
天天扭码1 小时前
总所周知,JavaScript中有很多函数定义方式,如何“因地制宜”?(ˉ﹃ˉ)
前端·javascript·面试
一个专注写代码的程序媛1 小时前
为什么vue的key值,不用index?
前端·javascript·vue.js
장숙혜2 小时前
ElementUi的Dropdown下拉菜单的详细介绍及使用
前端·javascript·vue.js