ts类型挑战【二十】

121 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第22天,点击查看活动详情

题目三十一:isnever

// template.ts
type IsNever = any
// test-cases.ts
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<IsNever<never>, true>>,
  Expect<Equal<IsNever<never | string>, false>>,
  Expect<Equal<IsNever<''>, false>>,
  Expect<Equal<IsNever<undefined>, false>>,
  Expect<Equal<IsNever<null>, false>>,
  Expect<Equal<IsNever<[]>, false>>,
  Expect<Equal<IsNever<{}>, false>>,
]

实现一个类型 IsNever,它接受输入类型 'T'

如果类型解析为 “never”,则返回 “true”,否则返回 “false”

测试用例

通过测试用例可以知道:

  • 传入 never 则返回 true
  • 传入其他任何内容都是 false

代码实现

  • 原代码
type IsNever<T> = any
  • never 不继承任何类型,所以这里使用 []处理
type IsNever<T> = [T] extends [never] ? true : false

题目三十二:isunion

// template.ts
type IsUnion<T> = any
// test-cases.ts
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<IsUnion<string>, false >>,
  Expect<Equal<IsUnion<string|number>, true >>,
  Expect<Equal<IsUnion<'a'|'b'|'c'|'d'>, true >>,
  Expect<Equal<IsUnion<undefined|null|void|''>, true >>,
  Expect<Equal<IsUnion<{ a: string }|{ a: number }>, true >>,
  Expect<Equal<IsUnion<{ a: string|number }>, false >>,
  Expect<Equal<IsUnion<[string|number]>, false >>,
  // Cases where T resolves to a non-union type.
  Expect<Equal<IsUnion<string|never>, false >>,
  Expect<Equal<IsUnion<string|unknown>, false >>,
  Expect<Equal<IsUnion<string|any>, false >>,
  Expect<Equal<IsUnion<string|'a'>, false >>,
]

实现一个类型 IsUnion,它接受输入类型 T,并返回 T 是否解析为联合类型

在条件类型中,联合将是分布的,看看下面的例子:

type Test<T, T2 = T> = T extends T2 ? {t: T, t2: T2} : true
type a = Test<string | number> // {t: string, t2: string | number} | {t: number, t2: string | number}
type b = Test<string> // {t: string, t2: string}

当分布发生时,T和T2之间存在差异

我们可以通过将T2赋回T来判断T是否为联合类型。我们用方括号来避免分配。

代码实现

  • 原代码
type IsUnion<T> = any
  • 设置类型 U
type IsUnion<T, U = T> = any
  • 约束 T 继承于 U
type IsUnion<T, U = T> = T extends U
	? ...
	: never
  • 因为条件类型中的联合类型是分布的,使用 [] 来避免分配
type IsUnion<T, U = T> = T extends U
	? [U] extends [T]
		? false // 两项相等,说明不是联合类型
		: true
	: never

题目三十三:replacekeys

// template.ts
type ReplaceKeys<U, T, Y> = any
// test-cases.ts
import type { Equal, Expect } from '@type-challenges/utils'

type NodeA = {
  type: 'A'
  name: string
  flag: number
}

type NodeB = {
  type: 'B'
  id: number
  flag: number
}

type NodeC = {
  type: 'C'
  name: string
  flag: number
}

type ReplacedNodeA = {
  type: 'A'
  name: number
  flag: string
}

type ReplacedNodeB = {
  type: 'B'
  id: number
  flag: string
}

type ReplacedNodeC = {
  type: 'C'
  name: number
  flag: string
}

type NoNameNodeA = {
  type: 'A'
  flag: number
  name: never
}

type NoNameNodeC = {
  type: 'C'
  flag: number
  name: never
}

type Nodes = NodeA | NodeB | NodeC
type ReplacedNodes = ReplacedNodeA | ReplacedNodeB | ReplacedNodeC
type NodesNoName = NoNameNodeA | NoNameNodeC | NodeB

type cases = [
  Expect<Equal<ReplaceKeys<Nodes, 'name' | 'flag', { name: number; flag: string }>, ReplacedNodes>>,
  Expect<Equal<ReplaceKeys<Nodes, 'name', { aa: number }>, NodesNoName>>,
]

实现一个类型 ReplaceKeys,替换联合类型中的键,如果某个类型没有这个键,只需跳过替换,一个类型需要三个参数。

测试用例

  • Equal<ReplaceKeys<Nodes, 'name' | 'flag', { name: number; flag: string }>, ReplacedNodes>

Nodes 中将键为 name 的字段,值修改为 number;将键为 flag 的字段,值修改为 string

  • Equal<ReplaceKeys<Nodes, 'name', { aa: number }>, NodesNoName>

准备在 Nodes 中修改键为 name 的字段,但是参数3中没有对应的键为 name 的字段,所以就将 Nodes 中键为 name 的字段,值修改为 never

代码实现

  • 原代码
    • U:联合类型
    • T:需要修改的字段名
    • Y:修改的字段值,需要和 T 进行匹配
type ReplaceKeys<U, T, Y> = any
  • 先排除 never 的情况

never 不继承自任何类型

type ReplaceKeys<U, T, Y> = U extends [never]
	? never
	: ...
  • 遍历 U,并判断每个遍历的键是否存在于 T 中。如果不存在,则说明无需修改,直接原值返回即可
type ReplaceKeys<U, T, Y> = U extends [never]
	? never
	: {[P in keyof U]: P extends T
    ? ...
    : U[P]}
  • 如果当前循环的键存在于 T 中,那么继续判断这个键是否存在于 Y 的键中,如果存在则使用 Y 的值替换,不存在则返回 never
type ReplaceKeys<U, T, Y> = U extends [never]
	? never
	: {[P in keyof U]: P extends T
    ? P extends keyof Y
     	? Y[P]
        : never
    : U[P]}