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?
CodePudding user response:
This is because typescript unions are not discriminated by the default.
type CommonProps = {a:number, b: number};
type OptionalProps = {c: number, d: number};
type Props = CommonProps | (CommonProps & OptionalProps)
const props2: Props = {a: 1, b: 2, c: 3}; // Shouldn't be valid but is???
Above type corresponds to CommonProps
because it already has a
and b
properties. c
property is not taken into account. Hence {a: 1, b: 2, c: 3}
is assignable to {a:number, b: number}
thats why it is valid.
SImilar case here:
type CommonProps = {a:number, b: number};
type OptionalProps1 = {c: number};
type OptionalProps2 = {d: number};
type Props = CommonProps & (OptionalProps1 | OptionalProps2)
const props4: Props = {a: 1, b: 2, c: 3, d: 4}; // Shouldn't be valid but is?
Props
should contain a
and b
and either c
or d
. This value {a: 1, b: 2, c: 3, d: 4}
is assignable to Props
because it has all of them. You can remove either c
or d
and it still will be valid.
You are looking for StrictUnion:
// credits goes to https://stackoverflow.com/questions/65805600/type-union-not-checking-for-excess-properties#answer-65805753
type UnionKeys<T> = T extends T ? 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 CommonProps = { a: number, b: number };
type OptionalProps1 = { c: number };
type OptionalProps2 = { d: number };
type Props = CommonProps & StrictUnion<(OptionalProps1 | OptionalProps2)>
const props4: Props = { a: 1, b: 2, c: 3 }; //ok
const props4_1: Props = { a: 1, b: 2, d: 3 }; //ok
const props4_2: Props = { a: 1, b: 2, d: 3, c: 4 }; // expected error
If you want to use discriminated unions you should add to every type in the union same property (for example kind
) and assign unique type.
CodePudding user response:
The |
operator indicates union types while the &
operator indicates intersection types. As is, I don't believe these can be combined to perform an XOR as you might want.
If you're trying to find an "equivalent" to XOR, I would suggest possibly the Exclude Utility Type, Discriminated Unions, or Conditional Types.