Item 63: Use Optional Never Properties to Model Exclusive Or
要点
- In TypeScript, "or" is "inclusive or":
A | B
means eitherA
,B
, or both. - Consider the "both" possibility in your code, and either handle it or disallow it.
- Use tagged unions to model exclusive or where it's convenient. Consider using optional
never
properties where it isn't. - 在 TypeScript 中,“或”是“包括性或”:
A | B
意味着可以是A
、B
,或者两者都有。 - 在代码中考虑“同时”的情况,并对其进行处理或禁止。
- 在方便的情况下,使用标记联合(tagged unions)来建模互斥或(exclusive or)。在不方便使用时,可以考虑使用可选的
never
属性。
正文
ts
interface ThingOne {
shirtColor: string
}
interface ThingTwo {
hairColor: string
}
type Thing = ThingOne | ThingTwo
ts
const bothThings = {
shirtColor: 'red',
hairColor: 'blue',
}
const thing1: ThingOne = bothThings // ok
const thing2: ThingTwo = bothThings // ok
ts
interface OnlyThingOne {
shirtColor: string
hairColor?: never
}
interface OnlyThingTwo {
hairColor: string
shirtColor?: never
}
type ExclusiveThing = OnlyThingOne | OnlyThingTwo
ts
const thing1: OnlyThingOne = bothThings
// ~~~~~~ Types of property 'hairColor' are incompatible.
const thing2: OnlyThingTwo = bothThings
// ~~~~~~ Types of property 'shirtColor' are incompatible.
const allThings: ExclusiveThing = {
// ~~~~~~~~~ Types of property 'hairColor' are incompatible.
shirtColor: 'red',
hairColor: 'blue',
}
ts
interface Vector2D {
x: number
y: number
z?: never
}
ts
function norm(v: Vector2D) {
return Math.sqrt(v.x ** 2 + v.y ** 2)
}
const v = { x: 3, y: 4, z: 5 }
const d = norm(v)
// ~ Types of property 'z' are incompatible.
ts
interface ThingOneTag {
type: 'one'
shirtColor: string
}
interface ThingTwoTag {
type: 'two'
hairColor: string
}
type Thing = ThingOneTag | ThingTwoTag
ts
type XOR<T1, T2> =
| (T1 & { [k in Exclude<keyof T2, keyof T1>]?: never })
| (T2 & { [k in Exclude<keyof T1, keyof T2>]?: never })
ts
type ExclusiveThing = XOR<ThingOne, ThingTwo>
const allThings: ExclusiveThing = {
// ~~~~~~~~~ Types of property 'hairColor' are incompatible.
shirtColor: 'red',
hairColor: 'blue',
}