type-challenges:Includes

46 阅读3分钟

Includes

问题描述

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

例如:

type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
// ============= Test Cases =============
import type { Equal, Expect } from './test-utils'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>>
]
​
// ============= Your Code Here =============
// 答案1
type Includes<T extends readonly unknown[], U> = T extends [infer P, ...infer R] ? (Equal<U, P> extends true ? true : Includes<R, U>) : false
                                                                                    
// 答案2
// type Includes<T extends readonly any[], U> = T['length'] extends 0 ? false : Equal<U, T[0]> extends true ? true : T extends [any, ...infer R] ? Includes<R, U> : false// 错误答案1
// type Includes<Value extends any[], Item> = Equal<Value[0], Item> extends true ? true : Value extends [Value[0], ...infer rest] ? Includes<rest, Item> : false
                                                                                 // 错误答案2 
// type Includes<T extends readonly any[], U> = Equal<U, T[0]> extends true ? true : T extends [any, ...infer R] ? Includes<R, U> : false

这道题稍微复杂一下,一般 typescript 中遇到需要循环数组的题,思路只有迭代和递归两种,在 javascript 中,举例:

// 此为迭代,函数只调用一次,不断改变一个变量的值,得到最终的结果。
function sum(number) {
  if (number < 0) return undefined;
  let total = 1;
  for (let n = number; n > 1; n--) {
    total = total * n;
  }
  return total;
}
console.log(sum(7)); // 5040// 此为递归,函数内部调用自身。
function sum(n) {
  // 基本条件
  if (n === 1 || n === 0) { 
    return 1;
  }
  // 递归调用
  return n * sum(n - 1); 
}
console.log(sum(7)); // 5040

答案一:这里应该不能使用迭代,循环用不了,所以使用递归的方式判断,思路是这样的,拿第二个参数和数组类型中的类型一个个做比较,不相同则跳过这个元素,T extends [infer P, ...infer R] 的意思是将数组类型分为两段,第一段 infer P 代表数组中的第一个类型会赋值给 P ,其余类型都分配给 R ,拿到 P 之后,和第二个泛型参数做比较,可以使用内置的 Equal 类型来判断,也可以自己定义 Equal 类型,我们这里使用内置的 Equal 类型来比较, Equal<R, U> extends true ,判断 `Equal<R, U>true 还是 false,如果是 true ,则代表第二个泛型参数存在于第一个泛型参数数组中,如果为 false,则继续跳过数组中的第一个类型,比对后续类型,将 R 当成第一个泛型参数传入到 Includes 类型中 Includes<R, U> ,直到全部判断完即可。

答案二:这里首先判断数组类型的长度,T['length'] extends 0 ,如果判断为 true,则直接返回,如果为 false ,则判断第二个泛型参数和第一个泛型参数的第一项是否相同,Equal<U, T[0]> extends true,如果为 true,则代表当前类型0存在于数组类型中,如果不存在,则继续判断数组的长度 T extends [any, ...infer R] 这代表至少有两项,R 如果存在,则将 R 当成第一个泛型参数传入到 Includes 类型中 Includes<R, U> ,后续逻辑同答案一。

错误答案一:这里测试用例 Expect<Equal<Includes<[null], undefined>, false>> 不通过,判断为true ,这是因为虽然第一个判断是不成立的,即 Equal<Value[0], Item> extends true 答案为 false ,继续走接下来的逻辑, Value extends [Value[0], ...infer rest],但是由于该泛型数组实际上只有一项,所以这里的 rest的类型即为 undefined,刚好在下一次 Includes 判断时条件成立,返回 true

错误答案二:这里和错误答案一的不同点在于参数约束多了一个 关键字 readonly,但实际上之后的判断和当前加不加 readonly 没有关系,错误的地方和错误答案一相同。