TypeScript currently (as of v2.5) lacks the ability to concatenate string literal types. When you concatenate two string literals in TypeScript, the resulting type is only known to be string. For example, it has no idea that the following is correct:
const x = "x";
const xx: "xx" = x + x; // error!
In your case, TypeScript infers A and B to be string values:
export const A = `${PATH}A`; // inferred as string
export const B = `${PATH}B`; // inferred as string
And therefore, an Action is not considered to be a discriminated union since the type property is the same in both cases:
export type Action =
{ type: typeof A, payload: { a: any } } |
{ type: typeof B, payload: { b: boolean } }
The only way around this for you to manually specify the literal types for A and B, possibly with a runtime check to make sure that you haven't misconfigured the constants. Yes, that's unfortunate, but it works:
const PATH = '@@test/';
export const A = "@@test/A";
export const B = "@@test/B";
if (!A.startsWith(PATH) || !B.startsWith(PATH)) {
throw new Error("Bad configuration");
}
Now, Action is a proper discriminated union and when you switch on the type property, TypeScript will narrow types for you automatically:
declare const action: Action;
switch (action.type) {
case A: {
const { a } = action.payload; // okay
break;
}
case B: {
const { b } = action.payload; // okay
break;
}
default:
const assertNever: never = action; // okay
break;
}
Hope that helps; good luck!