Typescript官方类型体操练习--easy系列题中{最难的一题}--【898・Includes】

63 阅读5分钟

前言

  • 2024年了,市场已经乱套了,公司倒闭数不胜数,就今年我已经遇到4次失业了,要么公司因资金问题搬迁了(搬到便宜的犄角旮旯里,根本不考虑员工上班是否方便,我要3小时路程,所以被迫劝退),要么项目直接崩了不要人了,要么本地项目直接都无了把人搞到别的非常远的城市(又被迫劝退),要么直接裁员的,哎,真实受够了;
  • 现在失业状态,又要开始刷题了,刷刷Typescript类型体操吧,说不定下家要面试用用(面试机会非常少,屈指可数),offer给到的薪资低的难以置信,为了尽量能去一家稳定的公司,这种薪资过于低的公司或者小公司搞不好哪天又无了,搞得简历非常难看,虽然已经很难看了,但是如果到了一家薪资低的干几个月又倒闭了,那到时候万一应聘一家还行的公司,一看银行流水工资很低的话也不好看。
  • 保险起见,只要条件允许,尽量找下家稳一点,也只能说尽量了,市场太乱了,只能耐心等一个坑位。
  • 本文记录的这一 Typescript 类型体操练习题是 easy 系列中最难的一题,为了便于记忆和理解,我在此记录下来,也顺便分享一下。

正文开始!!!

例如:

type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
  • 【答题处】
/*
  898 - Includes
  -------
  by null (@kynefuk) #easy #array

  ### Question

  Implement the JavaScript `Array.includes` function in the type system. A type takes the two arguments. The output should be a boolean `true` or `false`.

  For example:
  type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`

  > View on GitHub: https://tsch.js.org/898
*/

/* _____________ Your Code Here _____________ */

type Includes<T extends readonly any[], U> = ?? // <--------- 补全这里的 Includes

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/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>>,
]

  • 【题目关键知识点说明】
    • 这一题主要考验的是 Typescript 如何去实现判断两个类型是否完全相等,TS 本身只能通过 extends 去判断两个类型是否存在继承关系,所以要想判断两个类型是否完全相等的话,就需要自己写一个 Equal 类,当然也就是这个单测'@type-challenges/utils'中提供的 Equal,但是做题目就是为了完全理解这道题,所以不要想着直接去使用Equal,毕竟如果在实际项目里我们不会去直接引入这个单测库用在业务项目里的,所以我们需要自己在理解的情况下手写一个 MyEqual 来完成这道题;

【TS如何判断两个类型完全相等?】

  • 在 TypeScript 中,如果你希望判断两个类型是否完全相等,而不是仅检查是否存在继承关系,你可以使用条件类型和分布式条件类型来创建一个自定义的类型相等检查器。以下是一种方法。
  • 解释一下这个方法的工作原理:
    1. 条件类型:TypeScript 的条件类型允许你基于某个类型是否扩展另一个类型来返回不同的类型。
    1. 分布式条件类型:当你对联合类型使用条件类型时,TypeScript 会为联合类型中的每个成员应用条件,这就是所谓的分布式条件类型。
    1. 泛型函数:(() => T extends X ? 1 : 2) 和 (() => T extends Y ? 1 : 2) 这两个箭头函数实际上是用来创建一个新的类型环境,其中 T 是一个泛型参数。如果 T 可以扩展 X 或 Y,则返回 1,否则返回 2。
    1. 类型相等检查:如果这两个函数在结构上是相同的(即如果 T 扩展 X 的方式与 T 扩展 Y 的方式一致),那么我们可以认为 X 和 Y 是相等的。 这个方法的关键在于利用 TypeScript 的类型系统特性来模拟类型相等性检查。请注意,这种方法在非常复杂的类型上可能会遇到 TypeScript 的类型推断限制,但对于你的需求来说应该足够用了。
  • 所以用上面的思路去考虑的话,我们的 MyEqual 类型工具就能这样去写:
type MyEqual<X, Y> = 
  (<T>() => T extends X ? 1 : 2) extends 
  (<T>() => T extends Y ? 1 : 2) 
    ? true 
    : false;
    • 【重点再次解释】:<T>() => T extends X ? 1 : 2<T>() => T extends Y ? 1 : 2 叫做泛型函数,泛型函数是指在定义函数时使用泛型参数,使得函数的类型可以在调用时根据传入的参数动态决定,而不是固定写死。这样可以提高函数的灵活性和可复用性。所以我们这里的 MyEqual<X,Y> 泛型中的 X 和 Y 如果同时满足被某一个类型 T 扩展,那说明 X 和 Y 一定是完全相等的,所以(<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2)就能用来判断 X 和 Y 是否完全相等了

【完整答案】在此!!!

/*
  898 - Includes
  -------
  by null (@kynefuk) #easy #array

  ### Question

  Implement the JavaScript `Array.includes` function in the type system. A type takes the two arguments. The output should be a boolean `true` or `false`.

  For example:

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

  > View on GitHub: https://tsch.js.org/898
*/

/* _____________ Your Code Here _____________ */

// 自己实现 Equal 类型(为了完全理解此题,不用内置提供的 Equal[虽然一模一样],自己写为了完全理解)
type MyEqual<X, Y> = 
  (<T>() => T extends X ? 1 : 2) extends 
  (<T>() => T extends Y ? 1 : 2) 
    ? true 
    : false;

// 实现 Includes 类型
type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest] 
    ? MyEqual<First, U> extends true 
      ? true 
      : Includes<Rest, U>
    : false;


/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/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>>,
]