TypeScript 是否支持对象类型之间真正的 XOR 联合?
Does TypeScript support a true XOR union between object types?
我正在尝试为一个对象定义一个类型,该对象可以是一组道具或(同一组道具和所有另一组道具)。特别是,我无法理解为什么 TypeScript 认为 props2
的值是有效的,因为它不匹配类型联合的任何一侧:
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
在尝试理解 TypeScript 的联合方法时,我也 运行 遇到了 props4
中类似的意外行为:
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
在评估联合时,TypeScript 似乎允许对象在独立的每个字段基础上进行匹配,并且只强制类型在读取时遵循单个“路径”(如在可区分的联合中)。所以不清楚 TypeScript 联合是表示 XOR 运算还是简单的 OR 运算。
在尝试复制真正的 XOR 操作时,我尝试了(基于第一个示例):
type Props = (CommonProps & Record<keyof OptionalProps, never>) | (CommonProps & OptionalProps);
...但这也不起作用。
是否可以在 TypeScript 中定义一个类型,它只接受两种排他类型中的任何一种(而不是它们的混合类型)并且只允许读出这两种排他类型中的一种?
这是因为默认情况下不区分 typescript 联合。
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???
以上类型对应CommonProps
,因为它已经有a
和b
属性。 c
属性 没有考虑在内。因此 {a: 1, b: 2, c: 3}
可分配给 {a:number, b: number}
这就是它有效的原因。
类似的情况:
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
应包含 a
和 b
以及 c
或 d
。这个值 {a: 1, b: 2, c: 3, d: 4}
可以分配给 Props
因为它拥有所有这些。您可以删除 c
或 d
,它仍然有效。
您正在寻找:
// credits goes to
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
如果你想使用 discriminated unions,你应该向联合中的每个类型添加相同的 属性(例如 kind
)并分配唯一类型。
我正在尝试为一个对象定义一个类型,该对象可以是一组道具或(同一组道具和所有另一组道具)。特别是,我无法理解为什么 TypeScript 认为 props2
的值是有效的,因为它不匹配类型联合的任何一侧:
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
在尝试理解 TypeScript 的联合方法时,我也 运行 遇到了 props4
中类似的意外行为:
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
在评估联合时,TypeScript 似乎允许对象在独立的每个字段基础上进行匹配,并且只强制类型在读取时遵循单个“路径”(如在可区分的联合中)。所以不清楚 TypeScript 联合是表示 XOR 运算还是简单的 OR 运算。
在尝试复制真正的 XOR 操作时,我尝试了(基于第一个示例):
type Props = (CommonProps & Record<keyof OptionalProps, never>) | (CommonProps & OptionalProps);
...但这也不起作用。
是否可以在 TypeScript 中定义一个类型,它只接受两种排他类型中的任何一种(而不是它们的混合类型)并且只允许读出这两种排他类型中的一种?
这是因为默认情况下不区分 typescript 联合。
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???
以上类型对应CommonProps
,因为它已经有a
和b
属性。 c
属性 没有考虑在内。因此 {a: 1, b: 2, c: 3}
可分配给 {a:number, b: number}
这就是它有效的原因。
类似的情况:
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
应包含 a
和 b
以及 c
或 d
。这个值 {a: 1, b: 2, c: 3, d: 4}
可以分配给 Props
因为它拥有所有这些。您可以删除 c
或 d
,它仍然有效。
您正在寻找
// credits goes to
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
如果你想使用 discriminated unions,你应该向联合中的每个类型添加相同的 属性(例如 kind
)并分配唯一类型。