Blog.

TypeScript 可识别联合 Discriminated Unions

LiuuY

使用 TypeScript 时一个常见的需求:如何定义一个具有部分相同成员的类型。

例如,我们第一个 Pet 类型,它可能是狗汪汪叫,也可能是猫喵喵叫,同时都有体重和颜色两个属性:

type Pet = {
    type: '狗' | '猫';
    sound: '汪汪' | '喵喵';
    weight: number;
    color: string;
}

但是这个类型并不会阻止我们定一个「喵喵叫的狗」😄:

const dog: Pet = {
    type: 'dog',
    sound: 'neow', // 这里不会报错,
    weight: 10,
    color: 'gold'
}

这种情况我们可以使用可识别联合(Discriminated Unions):当 type/interface 中有有个字面量(literal)成员,我们可以使用它区分不同的联合。

我们先分别定义 DogCat 两个类型,其中包括了 typesound,两个字面量成员,然后分别(|)将其与共同的成员 weightcolor 组合(&)在一起:

type Dog = {
    type: 'dog';
    sound: 'woof';
}

type Cat = {
    type: 'cat';
    sound: 'neow';
}

type Pet = {
    weight: number;
    color: string;
} & (Dog | Cat);

const dog: Pet = {
    type: 'dog',
    sound: 'neow',
    weight: 10,
    color: 'gold'
}

此时「喵喵叫的狗」就会导致错误:

Type '{ type: "dog"; sound: "neow"; weight: number; color: string; }' is not assignable to type 'Pet'.
  Types of property 'sound' are incompatible.
    Type '"neow"' is not assignable to type '"woof"'.

而且我们也可以通过 Type Guards,在分支逻辑中帮助我们检查类型错误:

const howThePetSound = (pet: Pet) => {
    if (pet.type === 'dog') {
        console.log(pet.sound === 'neow') // 会报错
    }
}

错误信息:

This comparison appears to be unintentional because the types '"woof"' and '"neow"' have no overlap.