I'm trying to define a type for an object that is either a set of props OR (that same set of props AND all of another set of props). In particular, I'm having trouble understanding why TypeScript thinks props2's value is valid, since it doesn't match either side of the type union:
type CommonProps = {a:number, b: number};
type OptionalProps = {c: number, d: number};
type Props = CommonProps | (CommonProps & OptionalProps)
const props1: Props = {a: 1, b: 2}; // Should be valid and is
const props2: Props = {a: 1, b: 2, c: 3}; // Shouldn't be valid but is???
const props3: Props = {a: 1, b: 2, x: 3}; // Shouldn't be valid and indeed isn't
const props4: Props = {a: 1, b: 2, c: 3, d: 4}; // Should be valid and is
const props5: Props = {a: 1, b: 2, c: 3, d: 4, e: 5}; // Shouldn't be valid and indeed isn't
While trying to understand TypeScript's union method, I also ran into similar, unexpected behavior for props4 in:
type CommonProps = {a:number, b: number};
type OptionalProps1 = {c: number};
type OptionalProps2 = {d: number};
type Props = CommonProps & (OptionalProps1 | OptionalProps2)
const props1: Props = {a: 1, b: 2}; // Shouldn't be valid and indeed isn't
const props2: Props = {a: 1, b: 2, c: 3}; // Should be valid and is
const props3: Props = {a: 1, b: 2, x: 3}; // Shouldn't be valid and indeed isn't
const props4: Props = {a: 1, b: 2, c: 3, d: 4}; // Shouldn't be valid but is?
const props5: Props = {a: 1, b: 2, c: 3, d: 4, e: 5}; // Shouldn't be valid and indeed isn't
It would appear that in evaluating unions, TypeScript allows objects to flow in matching on independent per-field bases and only enforces that the types follow a single "path" upon read (as in discriminated unions). So it's unclear whether the TypeScript unions represent XOR operations or simply OR operations.
In trying to replicate a true XOR operation, I tried (based on the first example):
type Props = (CommonProps & Record<keyof OptionalProps, never>) | (CommonProps & OptionalProps);
... but that didn't work either.
Is it possible to define a type in TypeScript that only accepts either of two exclusive types (and not their hybrid) and only allows to be read out one of those two exclusive types?