typescript discriminated-union

typescript - La unión discriminada tipográfica permite un estado inválido



discriminated-union (1)

Estoy tratando de utilizar una unión discriminada de Typescript para modelar un escenario bastante común cuando se cargan datos de forma asíncrona:

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

Según mi entendimiento, esto debería limitar las combinaciones de valores permitidas de acuerdo con las definiciones de tipo. Sin embargo, el sistema de tipos acepta con gusto la siguiente combinación:

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

Espero un error aquí. ¿Hay algo que me esté faltando o de alguna manera mal uso de las definiciones de tipo?


Este es un problema con la forma en que el control de exceso de propiedad funciona en los sindicatos. Si se asigna un objeto literal a una variable de tipo de unión, una propiedad no se marcará como excedente si está presente en alguno de los miembros de la unión. Si no consideramos que el exceso de propiedades sea un error (y, a excepción de los literales de los objetos, no se consideran un error), el literal del objeto que especificó puede ser una instancia de LoadingState (una instancia con isLoading establecida en true como obligatorio y una pareja) de exceso de propiedades).

Para evitar este comportamiento no deseado, podemos agregar propiedades a LoadingState para que su objeto sea literalmente incompatible con 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!" }

Incluso podríamos crear un tipo que garantice que dicho miembro se agregará

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!" }