TypeScript 类型体操 03

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

00002-medium-return-type

/* _____________ Your Code Here _____________ */

type MyReturnType<T> = T extends (...params: any) => infer R ? R : never

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

type cases = [
  Expect<Equal<string, MyReturnType<() => string>>>,
  Expect<Equal<123, MyReturnType<() => 123>>>,
  Expect<Equal<ComplexObject, MyReturnType<() => ComplexObject>>>,
  Expect<Equal<Promise<boolean>, MyReturnType<() => Promise<boolean>>>>,
  Expect<Equal<() => 'foo', MyReturnType<() => () => 'foo'>>>,
  Expect<Equal<1 | 2, MyReturnType<typeof fn>>>,
  Expect<Equal<1 | 2, MyReturnType<typeof fn1>>>,
]

type ComplexObject = {
  a: [12, 'foo']
  bar: 'hello'
  prev(): number
}

const fn = (v: boolean) => v ? 1 : 2
const fn1 = (v: boolean, w: any) => v ? 1 : 2

00003-medium-omit

/* _____________ Your Code Here _____________ */

type MyOmit<T, K extends keyof T> = { [R in Exclude<keyof T, K>]: T[R] }

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

type cases = [
  Expect<Equal<Expected1, MyOmit<Todo, 'description'>>>,
  Expect<Equal<Expected2, MyOmit<Todo, 'description' | 'completed'>>>,
]

// @ts-expect-error
type error = MyOmit<Todo, 'description' | 'invalid'>

interface Todo {
  title: string
  description: string
  completed: boolean
}

interface Expected1 {
  title: string
  completed: boolean
}

interface Expected2 {
  title: string
}

需要说明的是,这里的MyOmit和TS中提供的Omit是不一样的,TS中的版本定义为:type Omit<T, K extends string | number | symbol> = { [P in Exclude<keyof T, K>]: T[P]; },不强制要求 Kkey of T中存在,但是这道题需要声明K extends keyof T 以满足type error = MyOmit<Todo, 'description' | 'invalid'>需要报错的测试用例

00008-medium-readonly-2

/* _____________ Your Code Here _____________ */

type MyReadonly2<T, K extends keyof T = keyof T> = { [R in Exclude<keyof T, K>]: T[R] } & { readonly [R in K]: T[R] }

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

type cases = [
  Expect<Alike<MyReadonly2<Todo1>, Readonly<Todo1>>>,
  Expect<Alike<MyReadonly2<Todo1, 'title' | 'description'>, Expected>>,
  Expect<Alike<MyReadonly2<Todo2, 'title' | 'description'>, Expected>>,
]

interface Todo1 {
  title: string
  description?: string
  completed: boolean
}

interface Todo2 {
  readonly title: string
  description?: string
  completed: boolean
}

interface Expected {
  readonly title: string
  readonly description?: string
  completed: boolean
}

这里最神奇的地方是:MyReadonly2<T, K extends keyof T = keyof T>中的K extends keyof T = keyof T他的意思是给K提供一个默认值,如果没有提供K,就使用默认值keyof T,真的是太神奇了。

00009-medium-deep-readonly

/* _____________ Your Code Here _____________ */

type DeepReadonly<T> = { readonly [K in keyof T]: keyof T[K] extends never ? T[K] : DeepReadonly<T[K]> }

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

type cases = [
  Expect<Equal<DeepReadonly<X>, Expected>>,
]

type X = {
  a: () => 22
  b: string
  c: {
    d: boolean
    e: {
      g: {
        h: {
          i: true
          j: 'string'
        }
        k: 'hello'
      }
      l: [
        'hi',
        {
          m: ['hey']
        },
      ]
    }
  }
}

type Expected = {
  readonly a: () => 22
  readonly b: string
  readonly c: {
    readonly d: boolean
    readonly e: {
      readonly g: {
        readonly h: {
          readonly i: true
          readonly j: 'string'
        }
        readonly k: 'hello'
      }
      readonly l: readonly [
        'hi',
        {
          readonly m: readonly ['hey']
        },
      ]
    }
  }
}

说实话这个地方我不太理解,有些很复杂的我觉得他是对的,但是太复杂了,也没搞懂。

00012-medium-chainable-options

/* _____________ Your Code Here _____________ */

type Chainable<T extends object = {}> = {
  option<K extends string, V extends unknown>(key: K extends keyof T ? never : K, value: V): Chainable<T & { [P in K]: V }>
  get(): T
}

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

declare const a: Chainable

const result1 = a
  .option('foo', 123)
  .option('bar', { value: 'Hello World' })
  .option('name', 'type-challenges')
  .get()

const result2 = a
  .option('name', 'another name')
  // @ts-expect-error
  .option('name', 'last name')
  .get()

type cases = [
  Expect<Alike<typeof result1, Expected1>>,
  Expect<Alike<typeof result2, Expected2>>,
]

type Expected1 = {
  foo: number
  bar: {
    value: string
  }
  name: string
}

type Expected2 = {
  name: string
}

话说这道题就离谱,还能这么搞的?简直大开眼界!有个知识点是:

大概意思是:如果T直接是一个Key, 就直接 {[p in T]: true},而不是:{[p in keyof T]: true}

00010-medium-tuple-to-union

/* _____________ Your Code Here _____________ */

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

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

type cases = [
  Expect<Equal<TupleToUnion<[123, '456', true]>, 123 | '456' | true>>,
  Expect<Equal<TupleToUnion<[123]>, 123>>,
]

00015-medium-last

/* _____________ Your Code Here _____________ */

type Last<T extends any[]> = T extends [...any[], infer res] ? res : never

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

type cases = [
  Expect<Equal<Last<[3, 2, 1]>, 1>>,
  Expect<Equal<Last<[() => 123, { a: string }]>, { a: string }>>,
]

00016-medium-pop

/* _____________ Your Code Here _____________ */

type Pop<T extends any[]> = T extends [... infer res, any] ? res : never

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

type cases = [
  Expect<Equal<Pop<[3, 2, 1]>, [3, 2]>>,
  Expect<Equal<Pop<['a', 'b', 'c', 'd']>, ['a', 'b', 'c']>>,
]

00015、00016是相似的,不过00016的写法我倒是之前没有想到会这样的。

Leave a Comment

Back to Top