在上篇中主要记录了使用ts的基本知识,本文主要记录如何实现ts内置的工具类型以及更多进阶版的工具类型。如果有问题,欢迎大佬指正。
类型体操其实是不难的,更多的套路。更重要的是上一张章节的内容
八:工具类型
工具类型可以理解为类似我们js中写的公共的工具方法。工具类型也可以称作类型体操,它是类型编程的一部分,在很多库中,函数的重载和参数的限制也是比较复杂的
内置工具类型
属性修饰用途
- Partial可选。 需要注意一个属性可选,不等于 原类型 | undefined,而是这个属性可以没有。这是因为ts是结构类型
type Partial<T> = { [p in keyof T]?: T[p] }
- Required必需
type Required<T> = { [p in keyof T] -?: T[p] }
- Readonly只读
type Readonly<T> = { readonly [p in keyof T] : T[p] }
- Mutable非只读
type Mutable<T> = { -readonly [p in keyof T] : T[p] }
结构用途
- Pick提取
type Pick<T, K extends keyof K> = { [key in K]: T[key] }
- Omit剔除
type Omit<T,K extends keyof any> = Pick<T, Exclude<keyof T, K>>
- Record定义,其实就是用了类型工具:索引签名
type Record<K, V> = { [name: K]: V }
联合类型
- 并集 a|b
- 交集
type Extract<T, K> = T extends K ? T : never
- 差集
type Exclude<T,K> = T extends K ? never : T
模式匹配
- 参数Parames
type Parames<T extends ()=>unknown> =
T extends (...reset: infer P) => unknown ? P : never
- 结果ReturnType
type ReturnType<T extends () => unknown> =
T extends (...args: any) => infer R ? R : any;
字符串大小写
- 字符串大写Uppercase
- 字符串小写Lowercase
- 首字母大写Capitalize
- 首字母小写Uncapitalize
- 上述字符串相关的这几个工具类型,你从ts内部是看不到具体实现的。实际上,ts底层就是调用js的方法来实现的
模板字符串类型
- 与js中模板字符串是一样的概念。接口的作用就是描述对象对外的接口,定义、限制了对象的结构。而模板字符串类型的作用与接口的作用是比较接近的
- 插槽内可以是单个字面量类型、上下文访问到的类型、字面量联合类型、原始类型
通过上下文类型
(就跟js的作用域链似的)
type World = 'world'
type Greet = `hello ${World}`
传入原始类型
- type Greet =
hello ${string};这个时候Greet类型并不会变成 Hello string,而是保持原样。这也意味着它并没有实际意义,此时就是一个无法改变的模板字符串类型,但所有 Hello 开头的字面量类型都会被视为 Hello ${string} 的子类型,如 Hello Linbudu、Hello TypeScript - 传入联合类型,type Phone =
${'iphone' | 'xiaomi'} - ${8 | 16}g - ${'白色‘ | ’黑色' };传入联合类型会自动组合成可能的类型,最终type Phone = 'iphone - 8g - 白色‘ | ... ; 如果组合出的类型里面有的不需要,例如iphone就没有16g的,很简单,Exclude剔除掉就行 - 泛型传入
type Greet<T extends string | number | boolean |
null | undefined | bigint> = `hello ${T}`
-
只有这些类型可以传入string | number | boolean | null | undefined | bigint
-
映射类型 + as这样的语法,叫做重映射。
type obj = { [k in "name" | "age" as `my-${k}`]: number };
- 基于重映射,重新实现PickByValue
// 之前
type ExpectPropKey<T extends object, valueType> = {
[k in keyof T]-?: T[k] extends valueType ? k : never;
}[keyof T];
type PickByValue<T extends object, valueType> = Pick<
T,
ExpectPropKey<T, valueType>
>;
// 现在
type PickByValue<T, ValueType> = {
[name in keyof T as T[name] extends ValueType ?
name : never]: T[name];
};
工具类型进阶
内置工具类型进阶
- 递归可选
type DeepPartial<T extends object> = { [k in keyof T]?:
T[k] extends object ? DeepPartial<T[k]> : T[k]
- 递归必需
type DeepRequired<T extends object> = {
[K in keyof T]-?: T[K] extends object ?
DeepRequired<T[K]> : T[K];
};
- 递归只读
type DeepReadonly<T extends object> = {
readonly[K in keyof T]: T[K] extends object
? DeepReadonly<T[K]> : T[K];
};
- 递归非只读
type DeepMutable<T extends object> = {
-readonly [K in keyof T]: T[K] extends object ? DeepMutable<T[K]>
: T[K];
};
属性修饰用途进阶:修饰部分
也就是拆分处理后,再组合
- 部分可选
type MarkPropsAsOption<T extends object, K extends keyof T
> = Partial<
Pick<T, K>
> &
Omit<T, K>;
- 部分必需
type MarkPropsAsRequired<T extends object, K extends keyof T> =
Required<Pick<T, K>> & Omit<T, K>;
- 部分只读
type MarkPropsAsReadonly<T extends object, K extends keyof T> =
Readonly< Pick<T, K> > & Omit<T, K>;
- 部分非只读
type Mutable<T> = {
-readonly [k in keyof T]: T[k];
};
type MarkPropsAsMutable<T extends object, K extends keyof T> =
Mutable< Pick<T, K> > & Omit<T, K>;
结构用途进阶:依据属性值类型
上面的工具类型,都是依据属性类型,进行的类型编程。也可以通过属性值的类型进行特殊的类型编程
- PickByValue
// 在联合类型中,never是无效的,会被忽略
// type nameList = 'zcc' | 'ltt' | never
// 实际上想当于type nameList = 'zcc' | 'ltt'
type ExpectPropKey<T extends object, valueType> = {
[k in keyof T]-?: T[k] extends valueType ? k : never;
}[keyof T];
type PickByValue<T extends object, valueType> = Pick<
T, ExpectPropKey<T, valueType>>;
- OmitByValue
type OmitByValue<T extends object, valueType> = Omit<
T, ExpectPropKey<T, valueType>>;
- 基于结构的互斥类型,不能同时拥有。比如说一个对象类型里面要么只有success属性,要么只有fail属性,而联合类型是不会满足这个需求的,你想想结构类型兼容性。如果 想要互斥,就应该将该属性值类型设为never。
type Without<T extends object, U extends object> = {
[key in Exclude<keyof T, keyof U>]: never;
};
type XOR<T extends object, U extends object> =
| (Without<T, U> & U)
| (Without<U, T> & T);
模式匹配进阶
- 提取函数第一个参数
type FirstParams<T extends (...args: any) => any> = T extends (
f: infer F, ...args: any ) => any ? F : never;
- 提取函数最后一个参数
type LastParams<T extends (...args: any) => any> = T extends (
...args: infer A
) => any
? A extends [...any, infer B]
? B
: never
: never;
- 提取对象必需属性
// 还记得之前说过一个属性为可选类型相当于什么?
// {name?: string} = {} | {name: string}
// 所以{}是{name?: string}的子集
// 方案一:
type PickByRequiredKeys<T extends object> = {
[name in keyof T]-?: {} extends Pick<T, name> ? never : name;
}[keyof T];
type PickByRequired<T extends object> =
Pick<T, PickByRequiredKeys<T>>;
// 方案二: 使用重映射
type PickByRequired<T extends Record<keyof any, unknown>> = {
[name in keyof T as {} extends Pick<T, name> ? never : name]: T[name]
}
- 提取对象可选属性
// 方案一
type PickByOptionalKeys<T extends object> = {
[name in keyof T]-?: {} extends Pick<T, name> ? name : never;
}[keyof T];
type PickByOptional<T extends object> =
Pick<T, PickByOptionalKeys<T>>;
// 方案二:使用重映射
type PickByOptional<T extends Record<keyof any, unknown>> = {
[name in keyof T as {} extends Pick<T, name> ? name : never]: T[name]
}
模板字符串进阶
- 去除左右的空格
type TrimLeft<T extends string> = T extends ` ${infer A}`
? TrimLeft<A> : T;
type TrimRight<T extends string> = T extends `${infer A} `
? TrimRight<A> : T;
type Trim<T extends string> = TrimLeft<TrimRight<T>>;
- 是否存在
type _Include<
T extends string,
S extends string
> = T extends `${infer A}${S}${infer B}` ? true : false;
// 因为字面量''只是字面量类型'',也就是自身的子集,所以需要单独判断出来
type Include<T extends string, S extends string> = T extends ""
? S extends ""
? true
: false
: _Include<T, S>;
- 删除(加个泛型,填充进去就是替换)
type Replace< T extends string, S extends string
> = T extends `${infer A}${S}${infer B}`
? `${A}${B}` : T;
- 全部删除(加个泛型,填充进去就是替换)
type ReplaceAll< T extends string, S extends string
> = T extends `${infer A}${S}${infer B}`
? Replace<`${A}${B}`, S> : T;
- 分隔符拆分
type Split<
T extends string,
Delimiter extends string
> = T extends `${infer A}${Delimiter}${infer B}`
? [A, ...Split<B, Delimiter>]
: T extends Delimiter
? []
: [T];
- 分隔符合并
type Join< List extends Array<string | number>,
Delimiter extends string > = List extends []
? "" : List extends [string | number]
? `${List[0]}` :
List extends [string | number , ...infer Rest extends (number | string)[]]
? `${List[0]}${Delimiter}${Join<Rest, Delimiter>}`
: string;