TypeScript 高级类型 - 可辨识联合

1,736 阅读2分钟

首先先来描述一个需求场景,例如有一个创建用户和更新用户的功能,其中要传给服务端的字段除了「更新」需要多一个「id」属性以外其余属性都一样。

在此我先定义一个接口来声明这个参数对象的类型:

interface Prop{
    action:'create'|'update';
    id?:string;
    name:string;
}

但是我们发现,这个接口并不满足我们的需求,因为我们这样创建对象依然会通过:

const a:Prop = {
    action:'create',
    id:'1',
    name:'zhangsan'
}

这个类型并不会通过 action 的值来帮我判断是否应该有 id 字段。

接下来换个写法来改进一下:

type Prop =
  | {
      action: "create";
      name: string;
    }
  | {
      action: "update";
      id: string;
      name: string;
    };

const a: Prop = {
  action: "update",
  name: "zhangsan"
};

这时如果我们声明一个 Prop 类型的变量没有 id 属性,TypeScript 就会报出异常:

Property 'id' is missing in type '{ action: "update"; name: string; }' but required in type '{ action: "update"; id: string; name: string; }'.

这就顺利解决了我们的问题,但是接着又遇到一个问题,当我在一个函数中传入 Prop 类型的参数,并使用了它的 id 属性时,TypeScript 又报了一个错误:

type Prop =
  | {
      action: "create";
      name: string;
    }
  | {
      action: "update";
      id: string;
      name: string;
    };

function fn(e: Prop) {
  console.log(e.id);
}
//  Property 'id' does not exist on type '{ action: "create"; name: string; }'.

原来是 TypeScript 并不确定你传进来的对象的 actionupdate 还是 create,所以继续改进一些代码来帮助 TypeScript 判断:

type Prop =
  | {
      action: "create";
      name: string;
    }
  | {
      action: "update";
      id: string;
      name: string;
    };

function fn(e: Prop) {
  if (e.action === "create") {
    console.log(e.name);
  } else {
    console.log(e.id);
  }
}

这样报错就顺利解决了。

我们一直在使用的 type Prop 这个类型就是一个可辨识联合,从上面的代码来看它有以下特点:

  1. 一个共有的字段。在上文中这个共有字段是 action
  2. 这个共有字段的值是可穷举的。因为如果值是不可穷举的,就无法用选择语句来对变量的具体类型进行判断。