Skip to content

字面量类型检查(可辨识联合类型)

再结合着对象的联合类型来看一下问题:

typescript
type UserTextEvent = { value: string; target: HTMLInputElement };
type UserMouseEvent = { value: number; target: HTMLButtonElement };
type UserEvent = UserTextEvent | UserMouseEvent;

function handle(event: UserEvent) {
  if (typeof event.value === "string") {
    console.log(event.value); // event.value类型为string
    console.log(event.target); // event.target类型为 HTMLInputElement | HTMLButtonElement
  } else {
    console.log(event.value); // event.value类型为number
    console.log(event.target); // event.target类型为 HTMLInputElement | HTMLButtonElement
  }
}

event.value的类型可以顺利的细化,但是event.target却不可以,因为 handle 函数的参数是UserEvent。联合之后的UserEvent,其实类似于:

typescript
type UserEvent = {
  value: string | number;
  target: HTMLInputElement | HTMLButtonElement;
};

也就是当value:string的时候,target可以选择HTMLInputElement | HTMLButtonElement

也就是当value:number的时候,target也可以选择HTMLInputElement | HTMLButtonElement

因此,Typescript 需要一种更可靠的方式,明确对象的并集类型的具体情况。

最常见的方式是,使用字面量类型进行标记,这样具体有值的情况下,就相当于在进行值的判断,这样 Typescript 就能很精确的推导出,具体的对象并集类型到底是哪个类型了

typescript
type UserTextEvent = {
  type: "TextEvent";
  value: string;
  target: HTMLInputElement;
};
type UserMouseEvent = {
  type: "MouseEvent";
  value: number;
  target: HTMLButtonElement;
};
type UserEvent = UserTextEvent | UserMouseEvent;

function handle(event: UserEvent) {
  if (event.type === "TextEvent") {
    console.log(event.value); // event.value类型为string
    console.log(event.target); // event.target类型为 HTMLInputElement
  } else {
    console.log(event.value); // event.value类型为number
    console.log(event.target); // event.target类型为 HTMLButtonElement
  }
}
handle({
  type: "TextEvent",
  value: "hello",
  target: document.getElementsByTagName("input")[0],
});

一般像这种多个类型的联合类型,并且多个类型含有一个公共可辨识的公共属性的联合类型,还有一个专门的称呼"可辨识联合类型"

可辨识联合类型对初学者有实际的指导作用,我们在创建类型的时候,就需要想着最好创建带有可辨识的联合类型,而不是可选字段

比如,有这样的情况,如果是circle的时候,有radius属性,如果是rect情况,有widthheight属性。对于初学者,很有可能创建成下面的类型:

typescript
type Shape = {
  kind: "circle" | "rect";
  radius?: number;
  width?: number;
  height?: number;
};

function area(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2; // error shape.radius可能未定义
    case "rect":
      return shape.width * shape.height; // error shape.width,shape.height可能未定义
  }
}

上面这种方式 kind 字段没有与其他字段建立关系,因此,不能保证可选属性是否有值。所以报出了未定义的错误(当然在后面的学习中我们可以使用非空断言!处理)。

可辨识的联合类型是一种更好的处理方式:

typescript
type Circle = { kind: "circle"; radius: number };
type Rect = { kind: "rect"; width: number; height: number };
type Shape = Circle | Rect;

function area(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "rect":
      return shape.width * shape.height;
  }
}