背景知识
Typescript的复合类型分为两类,
set和map,set是指一个无序的、无重复元素的集合,map就跟咱js的对象一样。
set 和 map相互转换
// map to set
interface IMap = {
a: string,
b: string
}
type sizeIns = keyof IMap; // 'a' | 'b'
// set to map
interface IMapIns = {
[k in sizeIns]: number
}
// output
// interface IMapIns = {
// a: number,
// b: number
// }
类型映射
TypeScript提供了从旧类型中创建新类型的一种方式 — 映射类型。 在映射类型里,新类型以相同的形式去转换旧类型里每个属性。
类型映射 - 同态变换
它们的共同点是只接受一个传入类型,生成的类型中key都来自于keyof传入的类型,value都是传入类型的value的变种。
type Partial<T> = { [P in keyof T]?: T[P] } // 将一个map所有属性变为可选的
type Required<T> = { [P in keyof T]-?: T[P] } // 将一个map所有属性变为必选的
type Readonly<T> = { readonly [P in keyof T]: T[P] } // 将一个map所有属性变为只读的
type Mutable<T> = { -readonly [P in keyof T]: T[P] } // ts标准库未包含,将一个map所有属性变为可写的
tips
-?: 是将可选项代表的?去掉, 从而让这个类型变成必选项。+?: 与-?相反,把属性变成可选的-readonly: 将readonly属性移除
在进行同态变换时,TS会先复制一遍传入参数的属性修饰符,再应用定义的变换。
interface Fruit {
readonly name: string
size: number
}
type PF = Partial<Fruit>; // PF.name既只读又可选,PF.size只可选
类型映射 - 其他类型变换
set 转换成 map
type Record<K extends keyof any, T> = { [P in K]: T };
type Size = 'small' | 'default' | 'big';
/*
{
small: number
default: number
big: number
}
*/
type SizeMap = Record<Size, number>;
保留map的一部分
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
/*
{
default: number
big: number
}
*/
type BiggerSizeMap = Pick<SizeMap, 'default' | 'big'>;
删除map的一部分
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
/*
{
default: number
}
*/
type DefaultSizeMap = Omit<BiggerSizeMap, 'big'>;
保留set的一部分
type Extract<T, U> = T extends U ? T : never;
type Result = 1 | 2 | 3 | 'error' | 'success';
type StringResult = Extract<Result, string>; // 'error' | 'success
删除set的一部分
type Exclude<T, U> = T extends U ? never : T;
type NumericResult = Exclude<Result, string>; // 1 | 2 | 3
获取函数返回值的类型
但要注意不要滥用这个工具类型,应该尽量多手动标注函数返回值类型。理由开篇时提过,契约高于实现。 用ReturnType是由实现反推契约,而实现往往容易变且容易出错,契约则相对稳定。另一方面,ReturnType过多也会降低代码可读性。
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
function f() { return { a: 3, b: 2}; }
/*
{
a: number
b: number
}
*/
type FReturn = ReturnType<f>
tips
infer的作用是让TypeScript自己推断,并将推断的结果存储到一个临时名字中,并且只能用于extends语句中。它与泛型的区别在于,泛型是声明一个“参数”,而infer是声明一个“中间变量”。