打字稿歧视联盟允许无效状态
Typescript Discriminated Union allows invalid state
我正在尝试使用 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!"
}
我预计这里会出现错误。是不是我遗漏了什么或者以某种方式滥用了类型定义?
这是超额 属性 检查联合的方式的问题。如果一个对象字面量被分配给一个联合类型的变量,一个 属性 如果它出现在联合成员的 any 上,则不会被标记为多余。如果我们不认为多余的属性是错误的(并且除了对象字面量之外,它们不被认为是错误),您指定的对象字面量可能是 LoadingState
的实例(具有 isLoading
的实例根据要求设置为 true
和一些多余的属性)。
为了避免这种不良行为,我们可以向 LoadingState
添加属性,使您的对象文字与 LoadingState
不兼容
type LoadingState = { isLoading: true; isSuccess?: undefined }
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 T ? keyof T : never;
type StrictUnionHelper<T, TAll> = T extends any ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, undefined>> : never;
type StrictUnion<T> = StrictUnionHelper<T, T>
type State = StrictUnion< LoadingState | SuccessState | ErrorState>
const testState: State = { // error
isLoading: true,
isSuccess: true,
errorMessage: "Error!"
}
我正在尝试使用 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!"
}
我预计这里会出现错误。是不是我遗漏了什么或者以某种方式滥用了类型定义?
这是超额 属性 检查联合的方式的问题。如果一个对象字面量被分配给一个联合类型的变量,一个 属性 如果它出现在联合成员的 any 上,则不会被标记为多余。如果我们不认为多余的属性是错误的(并且除了对象字面量之外,它们不被认为是错误),您指定的对象字面量可能是 LoadingState
的实例(具有 isLoading
的实例根据要求设置为 true
和一些多余的属性)。
为了避免这种不良行为,我们可以向 LoadingState
添加属性,使您的对象文字与 LoadingState
type LoadingState = { isLoading: true; isSuccess?: undefined }
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 T ? keyof T : never;
type StrictUnionHelper<T, TAll> = T extends any ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, undefined>> : never;
type StrictUnion<T> = StrictUnionHelper<T, T>
type State = StrictUnion< LoadingState | SuccessState | ErrorState>
const testState: State = { // error
isLoading: true,
isSuccess: true,
errorMessage: "Error!"
}