TypeScript 类型体操 02

发现一个好玩的开源项目:type-challenges,在上面可以做一些TypeScript类型相关的题目,这里记录一下自己的学习。

00043-easy-exclude

/* _____________ Your Code Here _____________ */

type MyExclude<T, U> = T extends U ? never : T

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<MyExclude<'a' | 'b' | 'c', 'a'>, Exclude<'a' | 'b' | 'c', 'a'>>>,
  Expect<Equal<MyExclude<'a' | 'b' | 'c', 'a' | 'b'>, Exclude<'a' | 'b' | 'c', 'a' | 'b'>>>,
  Expect<Equal<MyExclude<string | number | (() => void), Function>, Exclude<string | number | (() => void), Function>>>,
]

这里最神奇的是extends的用法,可以理解为,对于 T extends U,如果T是一个单一类型,那么就判断T是不是extends U,如果T是一个复合类型,就依次判断T中每一个元素是不是extends U

00189-easy-awaited

/* _____________ Your Code Here _____________ */

type MyAwaitedR<T> = T extends Promise<infer K> ? MyAwaitedR<K> : T
type MyAwaited<T> = T extends Promise<infer K> ? MyAwaitedR<K> : error

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type X = Promise<string>
type Y = Promise<{ field: number }>
type Z = Promise<Promise<string | number>>

type cases = [
  Expect<Equal<MyAwaited<X>, string>>,
  Expect<Equal<MyAwaited<Y>, { field: number }>>,
  Expect<Equal<MyAwaited<Z>, string | number>>,
]

// @ts-expect-error
type error = MyAwaited<number>

这里的关键点是:

  1. 有很多人答案是:type MyAwaited<T> = T extends Promise<infer R> ? MyAwaited<R> : T,但是这样不满足最后一个error的判断条件,因为MyAwaited<number> 返回的是 number不是error

  2. 我一开始写成:type MyAwaited<T> = T extends Promise<infer R> ? R extends Promise<infer M> ? M : R : error这样确实可以满足题目中所有的情况,但是如果把type Z = Promise<Promise<string | number>>改成type Z = Promise<Promise<Promise<string | number>>>就不行了,所以还是得用递归来实现。(这里无限套娃Promise应该是可以的吧?)

00268-easy-if


/* _____________ Your Code Here _____________ */

type If<C, T, F> = C extends true ? T : C extends false ? F : error

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<If<true, 'a', 'b'>, 'a'>>,
  Expect<Equal<If<false, 'a', 2>, 2>>,
]

// @ts-expect-error
type error = If<null, 'a', 'b'>

00533-easy-concat

/* _____________ Your Code Here _____________ */

type Concat<T extends readonly any[], U extends readonly any[]> = [...T, ...U]

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<Concat<[], []>, []>>,
  Expect<Equal<Concat<[], [1]>, [1]>>,
  Expect<Equal<Concat<[1, 2], [3, 4]>, [1, 2, 3, 4]>>,
  Expect<Equal<Concat<['1', 2, '3'], [false, boolean, '4']>, ['1', 2, '3', false, boolean, '4']>>,
]

00898-easy-includes


/* _____________ Your Code Here _____________ */

type Includes<T extends readonly any[], U> = T extends [infer X, ...infer Y] ? (Equal<X, U> extends true ? true: Includes<Y,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>>,
]

迷一样的898,我真搞不懂,这道题怎么会被分到简单里面的?答案参考的这里

但是我觉得这个方法也不是很完美,因为Equal这个东西是"@type-challenges/utils"里面的东西,不是TS自带的。

一开始,我是就是很简单得这么实现的:

后来参考了大佬的答案,我想实现一个自己的Equal函数,结果有两条过不去,经过测试,面对readonly属性有点无能为力

单独研究一下这个Equal是怎么实现的:

export type Equal<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false

说实话也没怎么看懂,网上有个专门讨论这个问题的帖子

但是这个Equal也不完美,他有个问题如下:

理论上 { a: string } & { b: string }{ a: string; b: string }这两个应该是相等的,但是这个Equal居然判断不相等。

这道题就先过吧。

03057-easy-push

/* _____________ Your Code Here _____________ */

type Push<T extends any[], U> = [...T, U]

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
import { ExpectFalse, NotEqual } from '@type-challenges/utils'

type cases = [
  Expect<Equal<Push<[], 1>, [1]>>,
  Expect<Equal<Push<[1, 2], '3'>, [1, 2, '3']>>,
  Expect<Equal<Push<['1', 2, '3'], boolean>, ['1', 2, '3', boolean]>>,
]

03060-easy-unshift

/* _____________ Your Code Here _____________ */

type Unshift<T extends any[], U> = [U, ...T]

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'
import { ExpectFalse, NotEqual } from '@type-challenges/utils'

type cases = [
  Expect<Equal<Unshift<[], 1>, [1]>>,
  Expect<Equal<Unshift<[1, 2], 0>, [0, 1, 2 ]>>,
  Expect<Equal<Unshift<['1', 2, '3'], boolean>, [boolean, '1', 2, '3']>>,
]

03312-easy-parameters

type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer R) => any ? R : never

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

const foo = (arg1: string, arg2: number): void => {}
const bar = (arg1: boolean, arg2: { a: 'A' }): void => {}
const baz = (): void => {}

type cases = [
  Expect<Equal<MyParameters<typeof foo>, [string, number]>>,
  Expect<Equal<MyParameters<typeof bar>, [boolean, { a: 'A' }]>>,
  Expect<Equal<MyParameters<typeof baz>, []>>,
]

Leave a Comment

Back to Top