Typescript 中带有布尔值的可区分联合

Discriminated unions with booleans in Typescript

为了跟踪 initializing/initialized 对象,我想创建一个带有布尔值的可区分联合。所以我写了下面的代码:

interface InitializingThing extends BaseThing {
    initialized: false;
    value: undefined;
}

interface InitializedThing extends BaseThing {
    initialized: true;
    value: number;
}

type Thing = InitializingThing | InitializedThing;

const thing: Thing = { initialized: false, value: undefined };
console.log(thing);

getThing().then((value: number) => {
    thing.value = value;
    thing.initialized = true;
}).then(() => {
    if (!thing.initialized) {
        return;
    }
    console.log(15 + thing.value);
});

(参见 Typescript playground

然而这给出了错误

Type 'number' is not assignable to type 'undefined'.(2322)
Type 'true' is not assignable to type 'false'.(2322)

我将鼠标悬停在 console.log(thing) 上可以看到类型是 InitializingThing 而不是 Thing!这似乎是问题的根源,但我不确定为什么 TS 编译器会这样做。

在这行代码中,对象无效:

thing.value = value;

它现在的类型是 {initialized: false, value: number},这是不正确的。

相反,您需要一起更改值:

thing = { value: value, initialized, true }

但是要做到这一点,您会发现 thing 不能是 const(因为您正在为其分配新值)。你需要做到 let.

您的代码原则上可以正常工作。然而,在上面的示例中,TS 实际上有点 "too smart" 及其 control flow analysis。在下面的变量赋值中

const thing: Thing = { initialized: false, value: undefined };

,编译器通过解释其初始化程序将 thing 的类型缩小为 InitializingThingthing 也假定保留此类型,因为变量是只读的/const。这就是 then 子句中的重新分配会触发错误的原因。

如果您强制 thing 真正属于 Thing 类型,给定的示例将再次编译:

const thing: Thing = { initialized: false, value: undefined } as Thing; // note the cast here
// or 
let thing: Thing = { initialized: false, value: undefined }; // switch to mutable `let`

Playground