TypeScript – 类型操作

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' }
]);