TS类型技巧(二):构造
TS类型系统不能对原类型进行修改,而是每次都构造新类型
extends + infer 提取类型,下一步便是 构造新类型
1. 构造数组
解构
type Push<Arr extends unknown[], Ele> = [...Arr, Ele];
type Zip<
T extends [unknown, unknown],
U extends [unknown, unknown]
> = T extends [infer A, infer B]
? U extends [infer C, infer D]
? [[A, C], [B, D]]
: never
: never
// [[1, 'a'], [2, 'b']];
type tuple1 = Zip<[1, 2], ['a', 'b']>
// 任意个元组类型构造Zip2(递归)
type Zip2< T extends unknown[], U extends unknown[] > =
T extends [infer FT, ...infer RT]
? U extends [infer FU, ...infer RU]
? [[FT, FU], ...Zip2<RT, RU>]
: []
: []
// [[1, 4], [2, 5], [3, 6]]
type testZip2 = Zip2<[1, 2, 3], [4, 5, 6]>
2. 构造字符串
解构, 正则, 内置工具类
// 下划线改驼峰
type CamelCase<Str extends string> =
Str extends `${infer Left}_${infer Right}${infer Rest}`
? `${Left}${Uppercase<Right>}${CamelCase<Rest>}`
: Str;
// "helloWorldTest"
type camelCase = CamelCase<'hello_world_test'>;
3. 构造函数
解构
// 加一个参数
type AppendArgument<T extends Function, arg> =
T extends (...args: infer Args) => infer ReturnType ?
(...args: [...Args, arg]) => ReturnType : never;
// (args_0: boolean) => void
type appendArgument = AppendArgument<() => void, boolean>;
4. 构造索引类型(重点)
keyof 生成Key的联合类型
in 进行key遍历,以修改value
as 修改key
修改value
type obj = {
name: string;
age: number;
gender: boolean;
}
// 基础用法,可在此基础上修改value
type Mapping<Obj extends object> = {
[Key in keyof Obj]: Obj[Key]
}
// { name: string; age: number; gender: boolean }
type mapping = Mapping<obj>;
修改key
as 修改key 叫做 Key Remapping(键重映射)
type UppercaseKey<Obj extends object> = {
[Key in keyof Obj as Uppercase<Key & string>]: Obj[Key]
}
// { NAME: string; AGE: number; GENDER: boolean; }
type uppercaseKey = UppercaseKey<obj>
type Record<K extends keyof any, T> = {
[P in K]: T;
}
// { [x: string]: number; [x: number]: number }
type record = Record<string | number, number>;
// 有了record之后,可以更具体约束为 key 为 string,值为任意类型的索引类型
type UppercaseKey2<Obj extends Record<string, any>> = {
[Key in keyof Obj as Uppercase<Key & string>]: Obj[Key]
}
& string 是 通用写法, Key 可能为string、number、symbol, 代表只取string
<K extends keyof any> 约束 K 是可以作为对象键的类型
K extends string | number | symbol 与这条完全相等
Record<string, any>
Record 专门用来创建索引类型,class、对象 等都是 索引类型
Obj extends Record<string, any> 对比 Obj extends Object
后者 无法确保是“纯粹的对象字面量结构”,
string, number, boolean, array, function 都属于 Object
以后都可以用Record<string, any>代替Obj extends Object
另外 <T, Key extends keyof T> 也是常见写法,
此时 Key 为 string | number | symbol, 使用 Uppercase需要Uppercase<Key & string>
删除key, as 条件类型
// 过滤只保留指定value类型的key
type FilterByValueType<
Obj extends Record<string, any>,
ValueType
> = {
[Key in keyof Obj as Obj[Key] extends ValueType
? Key
: never]: Obj[Key]
}
// { a: string }
type filterByValueType = FilterByValueType<
{ a: string; b: number },
string
>
Key in keyof Obj 遍历Obj的key,用 as 修改key
这些key 如果 extends ValueType,则保留该key,否则剔除
修改修饰符
type ToReadonly<T> = {
readonly [Key in keyof T]: T[Key];
}
type ToPartial<T> = {
[Key in keyof T]?: T[Key]
}
type ToMutable<T> = {
-readonly [Key in keyof T]: T[Key]
}
type ToRequired<T> = {
[Key in keyof T]-?: T[Key]
}
// 指定的 key 变 readonly
type ReadonlyObjectKey<
T extends Record<string, any>,
Key extends keyof T
> = {
readonly [K in Key]: T[K];
} & {
[K in Exclude<keyof T, Key>]: T[K]; // 其余保持原样
};
联合类型 归一化技巧
// 删除指定属性的readonly修饰符
type RemoveReadonly<
T extends Record<string, any>,
K extends keyof T
> =
// 1. 将 K 中的属性设为可变(移除 readonly)
{ -readonly [P in K]: T[P] }
// 2. 保留非 K 的其它属性(原样)
& { [P in Exclude<keyof T, K>]: T[P] }
// 对联合类型归一化
type RemoveReadonly2<
T extends Record<string, any>,
K extends keyof T
> =
{ -readonly [P in K]: T[P] }
& { [P in Exclude<keyof T, K>]: T[P] } extends infer O
// 3. 重新整理属性顺序(避免交叉类型不易使用)
? { [P in keyof O]: O[P] }
: never
// { name: string; } & { age: number; }
type test1 = RemoveReadonly<
{ readonly name: string; age: number },
'name'
>
// { name: string; age: number; }
type test2 = RemoveReadonly2<
{ readonly name: string; age: number },
'name'
>
extends infer O 这里是小type推导出大type,而之前常见的是大type中infer提取小type
type Exclude<T, U> = T extends U ? never : T T中排除U
type Extract<T, U> = T extends U ? T : never T中只保留U
上面两个内置类型同时还用到了 联合类的分发策略
其他
extends 就是 if条件判断
infer 就是 if条件内进行 解构+变量声明
递归 就是 for循环
in 是遍历
keyof 是解构索引类型
as 键重映射
神光大佬的TypeScript 类型体操通关秘籍