Skip to main content

TypeScript 实践

常见案例

以对象的 key 作为联合类型

const typeMap = {
1: 'a',
2: 'c',
3: 'g',
}

type Typekey = keyof typeof typeMap

以对象的 value 作为联合类型

type ValueOf<T> = T[keyof T]

const typeMap = {
1: 'a',
2: 'c',
3: 'g',
}

type TypeValue = ValueOf<typeof typeMap>

以对象的 value 值最为类型

// ts 会默认推导类型 `white` 的类型为 `string`
const color = {
white: '#fff',
black: '#000'
}

改成

// 此时 white 的类型为 ‘#fff’
const color = {
white: '#fff',
black: '#000'
} as const

以数组的每项元素作为联合类型

const arr = ['a', 'b', 'c'] as const
type Abc = typeof asd[number]

遍历联合类型

联合类型进行 extends 会进行分发,即一个接一个的判断是否 extends。

type LoopUnion<T extends string> = T extends T ? `loop ${T}` : never;

type AA = LoopUnion<'A' | 'B' | 'C'> // -> "loop A" | "loop B" | "loop C"

// 相当于是
type ABC = 'A' | 'B' | 'C'
('A' extends ABC ? 'loop A' : never) |
('B' extends ABC ? 'loop B' : never) |
('C' extends ABC ? 'loop C' : never)

判断

严格判断两个类型是否相当

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

MyEqual<any, unknown> // false
MyEqual<any, unknown> // false
MyEqual<1, 1 | 2> // false
MyEqual<{}, { a: 1 }> // false
MyEqual<{ readonly a: 1 }, { a: 1 }> // false
MyEqual<boolean, true> // false

MyEqual<{}, {}> // true
MyEqual<2 | 1, 1 | 2> // true

检查是否是 never

Conditional Types - Checking extends never only works sometimes #23182

在 ts 中,T extends U ? X : Y ,如果 T 是一个联合类型 'A' | 'B',将会“分发”(distributed),可以视作为

T = 'A' | 'B'
T extends U ? X : Y
// 将会视作为:
('A' extends U ? X : Y) | ('B' extends U ? X : Y)

在条件分发时,never 将会视为空联合类型(an empty union),意味着 'A' | never 被当作了 'A'

所以当 T 是 never 时,将不会分发,进而得到的结果是 never

type isNever<T> = T extends never ? true : false
type Res = isNever<never> // => never

所以我们需要强制让 TS 分发 T,让 T 不会被视为空联合类型。

type isNever<T> = [T] extends [never] ? true : false

在 K in S 中通过断言筛选

1367 - Remove Index Signature - Template literal alternative

type Foo = {
[key: string]: any
foo(): void
}

type RemoveIndexSignature<T> = {
[K in keyof T as K extends `${infer Str}` ? Str : never]: T[K]
}

Enum 转 Union

enum OrderStatus {
start,
pending,
end,
}

Enum key 转 Union

type StatusKey = keyof typeof OrderStatus

Enum value 转 Union

type StatusValue = `${OrderStatus}`

骚操作

重点在于使用 infer 提取,使用三元表达式递归。

字符串的提取

type StrShift = T extends `${infer Head}${U}` ? U : Never

数组的提取

type ArrShift = T extends [infer Head, ...infer Tail] ? Tail : Never

字符串 split

type Split<T, U extends string> = T extends `${infer Head}${U}${infer Tail}` ? [Head, ...Split<Tail, U>] : [T]

字符串 trim

type Whitespace = '\n' | ' ';

type Trim<T> = T extends `${Whitespace}${infer U}` ? Trim<U> : T extends `${infer U}${Whitespace}` ? Trim<U> : T;

type LeftTrim<T> = T extends `${WhiteSpace}${infer V}` ? LeftTrim<V> : T

数组 join

type Join<T extends unknown[], U extends string> = T extends [infer Head, ...infer Tail]
? [] extends Tail
? Head
: `${Head & string}${U}${Join<Tail, U>}`
: ''

数组转对象

[[k1, v1], [k2, v2]] 转成 { k1: v1, k2: v2 }

type MapArray = readonly (readonly [string, string])[]
type MapRecord = Record<string, string>
type ArrayToObject<Arr extends MapArray, Result extends MapRecord = {}> =
Arr extends []
? Result
: Arr extends readonly [infer Head, ...infer Tail]
? Head extends readonly [infer Key, infer Value]
? Tail extends MapArray
? Key extends string
? ArrayToObject<Tail, Result & Record<Key, Value>>
: never
: never
: never
: never