进阶类型

TypeScript 中的进阶类型

Union Type | 联合类型

TypeScript 中允许定义联合类型,即指定某个变量可能为 A 类型也可能为 B 类型:

let stringOrNumber: string | number = 1;
stringOrNumber = "hello";

很多时候,我们希望将某个常量数组转化为联合字符串类型,则可以利用 typeof 来进行定义:

const DATE_TIME_FIELDS = ["createdAt", "updatedAt", "deletedAt"] as const;

const a: typeof DATE_TIME_FIELDS[number];
// a: "createdAt" | "updatedAt" | "deletedAt"

Index Types | 索引类型

TypeScript 2.1 中为我们引入了 keyof 关键字,能够获取某个类型 T 的属性名列表,其返回结果也是联合类型,譬如:

interface Person {
  name: string;
  age: number;
  location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string

需要注意的是,keyof 关键字后面只能衔接 Interface 类型,如果我们希望获取某个正常的 JS 对象的键,那么需要先使用 typeof 关键字来获取到该对象对应的接口类型:

const Obj = {
  a: "b",
};

type K1 = keyof typeof Obj;

这即是所谓的索引存取类型,或者搜索类型,我们经常会将其用于限制参数的输入值:

function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key]; // Inferred type is T[K]
}

function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]) {
  obj[key] = value;
}

let x = { foo: 10, bar: "hello!" };

let foo = getProperty(x, "foo"); // number
let bar = getProperty(x, "bar"); // string

let oops = getProperty(x, "wargarbl"); // Error! "wargarbl" is not "foo" | "bar"

setProperty(x, "foo", "string"); // Error!, string expected number

Generics | 泛型

泛型允许我们灵活地定义某些函数或者类接收的参数类型,更易于创建灵活而可控地可重用组件,泛型函数定义格式如下:

<T>(items :T[], callback :(item :T) => T) :T[]

这里我们以简单地创建数组的函数为例:

function genericFunc<T>(argument: T): T[] {
  const arrayOfT: T[] = []; // Create empty array of type T.
  arrayOfT.push(argument); // Push, now arrayOfT = [argument].
  return arrayOfT;
}

const arrayFromString = genericFunc<string>("beep");
console.log(arrayFromString[0]); // "beep"
console.log(typeof arrayFromString[0]); // String

const arrayFromNumber = genericFunc(42);
console.log(arrayFromNumber[0]); // 42
console.log(typeof arrayFromNumber[0]); // number
// 接口泛型
interface Pair<T1, T2> {
  first: T1;
  second: T2;
}

// 泛型类属性
class Pair<T> {
  fst: T;
  snd: T;
}

我们还可以指定泛型子类,即指定某个类型必须是实现某个接口或者继承自某个类:

interface HasLength {
  length: number;
}

function addLengths<T extends HasLength>(t1: T, t2: T): number {
  return t1.length + t2.length;
}

addLengths("hello", "abc");
addLengths([1, 2, 3], [100, 11, 99]);

TypeScript 2.3 之后支持泛型默认参数,可以某些场景减少函数类型重载的代码量,譬如:

declare function create<T extends HTMLElement = HTMLDivElement, U = T[]>(
  element?: T,
  children?: U
): Container<T, U>;

类泛型

在编码过程中,我们经常会需要根据传入的类型来动态创建该类型的对象,其编写方式如下:

// 直接设置类型为参数会抛出异常
function activatorNotWorking<T extends IActivatable>(type: T): T {
  return new T(); // compile error could not find symbol T
}

// 应该以如下方式实现
function activator<T extends IActivatable>(type: { new (): T }): T {
  return new type();
}

const classA: ClassA = activator(ClassA);

某个实例如下:

class TestBase {
  hi() {
    alert("Hi from base");
  }
}

class TestSub extends TestBase {
  hi() {
    alert("Hi from sub");
  }
}

class TestTwo<T extends TestBase> {
  constructor(private testType: new () => T) {}

  getNew(): T {
    return new this.testType();
  }
}

//let test = new TestTwo<TestBase>(TestBase);
let test = new TestTwo<TestSub>(TestSub);

let example = test.getNew();
example.hi();

Mapped Types

Partial Type | 偏类型

在实际开发中,我们往往只希望用到某个接口的部分属性,特别是在实体类的定义中:

interface UserModel {
  email: string;
  password: string;
  address: string;
  phone: string;
}

class User {
  // 这里强制传入完全符合 UserModel 结构定义的对象,否则会抛出错误
  update(user: UserModel) {
    // Update user
  }
}

如果我们将接口属性定义为了可选属性,那么又会面临大量的空判断;TypeScript 2.1 之后为我们提供了 Partial 关键字,其内部的类型声明类似于:

type Partial<T> = { [P in keyof T]?: T[P] };

我们可以用其声明部分校验:

class User {
  update(user: Partial<UserModel>) {
    // Update user
  }
}

type ComponentConfig = {
  optionOne: string;
  optionTwo: string;
  optionThree: string;
};

// 这里的使用场景是传入部分配置项
export class SomeComponent {
  private _defaultConfig: Partial<ComponentConfig> = {
    optionOne: "...",
  };
}

Partial 同样能够用于类的声明中:

type RectangleShape = Partial<Shape & Perimeter> & Point;

我们也可以借鉴 Partial 的思想封装 DeepPartial,即深度嵌套:

export type DeepPartial<T> = {
  [key in keyof T]?: DeepPartial<T[key]>;
};

TypeScript 还为我们提供了 Pick 与 Record 类型,Pick 类型允许我们定义仅包含目标类型中的部分属性:

// From T pick a set of properties K
declare function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K>;

const nameAndAgeOnly = pick(person, "name", "age"); // { name: string, age: number }

Links

上一页
下一页