打字稿歧视联盟允许无效状态

时间:2018-10-06 09:23:32

标签: typescript discriminated-union

我正在尝试使用Typescript Discriminated Union为异步加载数据时的一个相当常见的情况建模:

type LoadingState = { isLoading: true; }
type SuccessState = { isLoading: false; isSuccess: true; }
type ErrorState =   { isLoading: false; isSuccess: false; errorMessage: string; }

type State = LoadingState | SuccessState | ErrorState;

根据我的理解,这应该根据类型定义限制允许的值组合。但是,类型系统很乐意接受以下组合:

const testState: State = {
    isLoading: true,
    isSuccess: true,
    errorMessage: "Error!"
}

我希望这里有一个错误。我是否缺少某些东西,或者以某种方式滥用了类型定义?

1 个答案:

答案 0 :(得分:12)

这是工会对多余财产进行检查的方式的问题。如果将对象文字分配给联合类型的变量,则如果属性存在于联合成员的任何中,则该属性不会被标记为多余。如果我们不认为多余的属性是一个错误(除了对象文字,它们不被视为错误),则您指定的对象文字可以是LoadingState的实例(带有isLoading的实例根据要求设置为true和一些多余的属性。

要解决这种不良行为,我们可以向LoadingState添加属性,以使您的对象文字与LoadingState不兼容

type LoadingState = { isLoading: true; isSuccess?: never }
type SuccessState = { isLoading: false; isSuccess: true; }
type ErrorState =   { isLoading: false; isSuccess: false; errorMessage: string; }

type State = LoadingState | SuccessState | ErrorState;

const testState: State = { // error
    isLoading: true,
    isSuccess: true,
    errorMessage: "Error!"
}

我们甚至可以创建一种类型,以确保将添加此类成员

type LoadingState = { isLoading: true; }
type SuccessState = { isLoading: false; isSuccess: true; }
type ErrorState =   { isLoading: false; isSuccess: false; errorMessage: string; }

type UnionKeys<T> = T extends any ? keyof T : never;
type StrictUnionHelper<T, TAll> = T extends any ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
type StrictUnion<T> = StrictUnionHelper<T, T>

type State = StrictUnion< LoadingState | SuccessState | ErrorState>

const testState: State = { // error
    isLoading: true,
    isSuccess: true,
    errorMessage: "Error!"
}