TS 进阶3 - 工具类型 Tool Types

You, TypeScript
Back

1. 手写几个原生的工具类型

{
/** 首先写一个Partial
*/
type Partial<T>= {
[K in keyof T]?:T[K]
}
/** 修饰符
去除可选修饰符:-?
只读修饰符:readonly
去除只读修饰符:-readonly
*/
/** 实现一个 Required
*/
type Required<T> = {
[K in keyof T] -?: T[K]
}
/** Readonly
*/
type Readonly<T> = {
readonly [K in keyof T] : T[K]
}
}

2. Pick, Exclude 和 Omit

/** Pick 和 Omit
* Pick 选取传入的键值
* Omit 过滤传入的键值
*/
{
// Pick 类型, 从对象中提取出来自一个 Union 范围的键对应的键值对
type Pick<T, K extends keyof T> = {
[P in K] : T[P]
}
const x = {a: 'a', b: 'b', c: 'c',d: 'd'}
const y : Pick<typeof x, 'a'|'b'> = {a:'a', b:'b'}
// Omit 类型, 从对象中去除来自一个 Union 范围内的键值对
type Omit<T, K extends keyof T> = {
[P in K]: never
}
}
{
/** 实现一个 Exclude<T, U>
* T 是被筛选的候选人, U 是筛选条件
* 用条件类型来实现
* 此时可以利用分布式条件类型的特性, 让 Ts 来分发结果
*/
type Exclude<T, U> = T extends U? never: T
type A = Exclude<1 | 2 | 3 | 4 | 5, 2|4>
type Omit<T,K extends keyof any>= Pick<T, Exclude<keyof T, K>>
}

3. 用 infer 来推导返回值类型 ReturnType, 参数类型 Parameters, 构造器参数类型 ConstructorParameters, 实例类型 InstanceType

{
/** 用 infer 实现 ReturnType 和 Parameters, ConstructorParameter, InstanceType
*/
// 推导函数返回值
type ReturnType<T extends (...args: any[])=> any> =
T extends (...args: any[])=> infer R? R: any
// 推导函数参数
type Parameters<T extends (...args: any[])=> any> =
T extends (...args: infer P) => any ? P: never
// 推导构造器入参
type ConstructorParameters<T extends new (...args: any) => any> =
T extends new (...args: infer P)=> any ? P: any
type InstanceType<T extends new (...args: any) => any> =
T extends new (...args: any) => infer R? R: any
}

4. TypeScript 4.7 新增的模板字面量类型 ${}

{
/** 模板字面量类型
* TypeScript 4.7 引入了模板字面量类型, 使得我们可以使用模板字符串语法 ${} 来构造字面量类型.
*/
type World = 'world';
type Greeting = `hello ${World}`
/** 随之而来的4种新的工具类型 Uppercase, Lowercase, Capitalize, Uncapitalize
*/
const upperCaseWorld: Uppercase<World> = 'WORLD'
const lowercaseWorld: Lowercase<World> = 'world'
const capitalizedWorld: Capitalize<World> = 'World'
const uncapitalizedWorld: Uncapitalize<World> = 'world'
}

5. 社区工具类型

{
/** 社区工具类型
*/
type Primitive =
| string
| number
| bigint
| boolean
| symbol
| null
| undefined
const isPrimitive = (val: unknown): val is Primitive => {
if (val === null || val === undefined) {
return true
}
const typeDef = typeof val
const primitiveNonNullishTypes = [
'string',
'number',
'bigint',
'boolean',
'symbol'
]
return primitiveNonNullishTypes.indexOf(typeDef) !== -1
}
type Nullish = null | undefined
type NonUndefined<A> = A extends undefined? never : A
type NonNullable<A> = A extends null | undefined? never: A
}

工具类型实战

提取出 Promise 返回的类型

{
// 提取 Promise 的实际类型
const foo = (): Promise<string> => {
return new Promise((resolve, reject) => {
resolve('xyz');
})
}
type FooReturnType = ReturnType<typeof foo>
type PromiseType<T extends Promise<any>> = T extends Promise<infer R>? R: never
type FooPromiseReturnType = PromiseType<FooReturnType>
}

实现递归的工具类型

{
/** 递归的工具类型 (通过条件类型): DeepPartial, DeepRequired, DeepMutable, DeepReadonly
* 对于 Partial, Readonly, Required 等对类型的 key 进行循环的类型,
* 如果 value[key] 的类型是一个 Record, 那么我们可能需要递归的工具类型来定义类型.
*/
type DeepPartial<T> = {
[K in keyof T]?:
T[K] extends object
? DeepPartial<T[K]>
: T[K]
}
type DeepRequired<T> ={
[K in keyof T]-?:
T[K] extends object
? DeepRequired<T[K]>
: T[K]
}
type DeepMutable<T> = {
-readonly [K in keyof T]:
T[K] extends object
? DeepMutable<T[K]>
: T[K]
}
type DeepReadonly<T> ={
+readonly [K in keyof T]:
T[K] extends object
? DeepReadonly<T[K]>
: T[K]
}
}

返回指定特征键的类型联合

{
/** 返回值类型确定的键名联合的工具类型
*/
type FunTypeKeys<T extends object> = {
[K in keyof T]-?: T[K] extends Function? K: never
}[keyof T]
/** 如何理解 {[K in keyof T]: ...}[keyof T] ?
* 可以理解为通过遍历 key 得到一个联合
*
* example:
*/
interface TypeWithFuncKey {
a: string;
b: number;
c: boolean;
d: () => void;
e: () => void;
}
type MiddleState<T extends object> = {
[K in keyof T] -?: T[K] extends Function? K: never
}
/** 得到 { x: never, y: 'y'} 这样的类型
* */
type MiddleType = MiddleState<TypeWithFuncKey>
/** 将类型为 never 的字段过滤, 得到类型为 Function 的 (key 字面量) 联合
* */
type WhatWeWant = MiddleType[keyof TypeWithFuncKey]
}

得到可选字段的键, 非可选类型的键

{
/** 得到可选字段的类型, 非可选字段的类型
* 思路: 用 {} extends Pick<T,K> ? 来确定 Pick<T,K> 是否得到了结果 (字段是必选)
* {} extends {} 为true
* {} extends {xx:any} 为 false
*/
type OptionalTypes<T> = {
[K in keyof T]-?: {} extends Pick<T,K>? K: never
}[keyof T]
type RequiredTypes<T> ={
[K in keyof T]-?: {} extends Pick<T,K>? never: K
}[keyof T]
type Student = {
name: string,
age?: number,
gender?: string
}
type Z = {} extends Pick<Student, 'age'>? 'optional': 'required'
type StudentOptionType = OptionalTypes<Student>
type StudentRequiredType = RequiredTypes<Student>
type EmptyObject = Pick<Student, never>
}
© Apolo Du.