TypeScript – 类型操作
1. Pick<T, K>
考虑下列的场景,有两个 Interface,SubState 是 State 的子集,相同字段类型相同,我们可以这样定义:
interface State {
field1: string;
field2: number;
field3: string;
}
interface SubState {
field1: string;
field2: number;
}
可以观察到,这样的实现明显不符合 DRY(Don’t Repeat Yourself)原则。这导致一个问题,当需要修改State 接口内定义的类型时,SubState 也要同步修改,容易出错。面对这种情况,可以通过索引到 State 来消除属性的重复。
type SubState = {
field1: State['field1'];
field2: State['field2'];
}
这样写解决了类型同步的问题,但是书写比较冗长。这时候可以使用一个映射类型来代替上述的写法,得到的结果是一致的。
type SubState = {
[k in 'field1' | 'field2']: State[k]
}
映射类型在实际开发中非常常见,以至于 TypeScript 在标准库中实现了这样的类型操作,即 Pick。
type Pick<T, K> = { [k in K]: T[k] };
对应的,SubState 可以改写成下面这种形式:
type SubState = Pick<State, 'field1' | 'field2'>;
// { field1: string, field2: number }
2. Partial<T>
考虑下列场景,定义了一个满足接口 Options 的类,后续的更新需要限制只能接收 Options 定义的字段:
interface Options {
field1: string;
field2: number;
}
interface UpdateOptions {
field1?: string;
field2?: number;
}
class Test {
construct(init: Options) {};
update(options: UpdateOptions) {};
}
同样的,可以使用一个映射类型和 keyof 从Options中创建 UpdateOptions。
type UpdateOptions = { [k in keyof Options]?: Options[k] };
TypeScript的标准库内也有同样的实现:
type Partial<T> = { [P in keyof T]?: T[P] | undefined; }
对应的,UpdateOptions 可以改写成下面这种形式:
type UpdateOptions = Partial<Options>;
class Test {
construct(init: Options) {};
update(options: UpdateOptions) {};
}
3. keyof
如果想定义一个和一个值样子相同的类型,可以使用此方法。
const INIT_OPTIONS = {
field1: 1,
field2: '2',
}
interface Options {
field1: number;
field2: string;
}
同样的,Options 可以改写为:
type Options = typeof INIT_OPTIONS;
4. ReturnType<keyof T>
通过上面的 keyof 操作,可以为一个函数或者方法的推断返回值创建一个命名类型。
function getDataInfo() {
return {
field1: 1,
field2: '2'
}
}
// 返回类型为:{ field1: number, field2: string }
getDataInfo 的返回值的命名类型可以写成:
type DataInfo = ReturnType<keyof getDataInfo>;
5. extends
可以使用 extends 来约束通用类型中的参数。
interface Options {
field1: string;
field2: string;
}
type IOption<T extends Options> = [T, T];
目前 TypeScript 要求必须在声明时明确写出通用参数,即
const instance: IOption<Options> = [
{ field1: '1', field2: '2' },
{ field1: '3', field2: '4' }
];
当然,也可以使用一个类型化的等身函数让 TypeScript 能够自动推断出泛型参数的类型
const Opt = <T extends Options>(x: IOption<T>) => x;
const instance = Opt([
{ field1: '1', field2: '2' },
{ field1: '3', field2: '4' }
]);