Skip to content

中等

2 - 获取函数返回类型

ts

/* _____________ 你的代码 _____________ */

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

/* _____________ 测试用例 _____________ */
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

/* _____________ 下一步 _____________ */
/*
  > 分享你的解答:https://tsch.js.org/2/answer/zh-CN
  > 查看解答:https://tsch.js.org/2/solutions
  > 更多题目:https://tsch.js.org/zh-CN
*/

3 - 实现 Omit

ts
/* _____________ 你的代码 _____________ */

// type MyOmit<T, K extends keyof T> = { [P in Exclude<keyof T, K>]: T[P] }
type MyOmit<T, K extends keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P]
}
/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

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

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

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

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

interface Expected1 {
  title: string
  completed: boolean
}

interface Expected2 {
  title: string
}

interface Expected3 {
  readonly title: string
}

/* _____________ 下一步 _____________ */
/*
  > 分享你的解答:https://tsch.js.org/3/answer/zh-CN
  > 查看解答:https://tsch.js.org/3/solutions
  > 更多题目:https://tsch.js.org/zh-CN
*/

思考: why use as?

Without the as keyword, the keys would be interpreted as plain property keys, and the conditional type exclusion based on P extends K would not work as expected, so it's main goal is to map the keys of the index signature.

https://github.com/type-challenges/type-challenges/issues/448#issuecomment-1875157558

8 - 对象部分属性只读

ts
/* _____________ 你的代码 _____________ */

type MyReadonly2<T, K extends keyof T = keyof T> = { readonly [key in K]: T[key] } & Omit<T, K>
/* _____________ 测试用例 _____________ */
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>>,
  Expect<Alike<MyReadonly2<Todo2, 'description'>, Expected>>,
]

// @ts-expect-error
type error = MyReadonly2<Todo1, 'title' | 'invalid'>

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
}

/* _____________ 下一步 _____________ */
/*
  > 分享你的解答:https://tsch.js.org/8/answer/zh-CN
  > 查看解答:https://tsch.js.org/8/solutions
  > 更多题目:https://tsch.js.org/zh-CN
*/

9 - 对象属性只读(递归)

ts
/* _____________ 你的代码 _____________ */

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

/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<DeepReadonly<X1>, Expected1>>,
  Expect<Equal<DeepReadonly<X2>, Expected2>>,
]

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

type X2 = { a: string } | { b: number }

type Expected1 = {
  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']
        },
      ]
    }
  }
}

type Expected2 = { readonly a: string } | { readonly b: number }

思考: 怎么判断类型是不是对象类型?

对象类型:如果 T 是一个对象类型,例如 { name: string, age: number },那么 keyof T 将是对象的键组成的联合类型。在这个例子中,keyof T 会是 'name' | 'age'。

非对象类型:如果 T 是基本类型,如 number、string,那么它没有键,因此 keyof T 将是 never。

因此,当 keyof T 结果是 never 时,说明 T 不是对象类型。

10 - 元组转合集

ts
/* _____________ 你的代码 _____________ */

// answer1
// type TupleToUnion<T extends Array<unknown>> = T[number]

// answer2
type TupleToUnion<T> = T extends Array<infer ITEMS> ? ITEMS : never

/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

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

/* _____________ 下一步 _____________ */
/*
  > 分享你的解答:https://tsch.js.org/10/answer/zh-CN
  > 查看解答:https://tsch.js.org/10/solutions
  > 更多题目:https://tsch.js.org/zh-CN
*/

MIT Licensed