TL;DR

  • 型空間の | は包含的論理和なので、A | B は「A か B、またはその両方」を意味する。
  • 排他的論理和は、never 型のオプションプロパティかタグ付きユニオンでモデリングできる。

内容

型空間の | は包含的論理和

型空間での | は包含的論理和であり、値空間での || と同じである。したがって、TypeScript は構造的型付けを採用しているので(項目4 構造的型付けに慣れる)、A | B は A か B、またはその両方を意味する。ただし、余剰プロパティチェックによる制限は受ける(項目11 余剰プロパティチェックと型チェックを区別する)。

interface ThingOne {
  shirtColor: string
}
interface ThingTwo {
  hairColor: string
}
type Thing = ThingOne | ThingTwo
 
const bothThings = { shirtColor: "red", hairColor: "blue" }
const thing1: ThingOne = bothThings // OK
const thing2: ThingTwo = bothThings // OK

never 型のオプションプロパティで排他的論理和を表す

排他的論理和(A か B のどちらか一方)は、never 型のオプションプロパティでモデリングできる。never 型には代入できる値がないので、そのプロパティは持てなくなる。

interface OnlyThingOne {
  shirtColor: string
  hairColor?: never
}
interface OnlyThingTwo {
  hairColor: string
  shirtColor?: never
}
type ExclusiveThing = OnlyThingOne | OnlyThingTwo
 
// 以下はすべて型エラー
const thing1: OnlyThingOne = bothThings
const thing2: OnlyThingTwo = bothThings
const allThings: ExclusiveThing = { shirtColor: "red", hairColor: "blue" }

次のような汎用的な型を定義すれば、毎回 never を書かずに済む。この方法は、タグを追加できない場合に役立つ。

type XOR<T1, T2> =
  | (T1 & { [k in Exclude<keyof T2, keyof T1>]?: never })
  | (T2 & { [k in Exclude<keyof T1, keyof T2>]?: never })
 
type ExclusiveThing = XOR<ThingOne, ThingTwo>

タグ付きユニオンで排他的論理和を表す

文字列が 'one' と 'two' の両方になることはないので、タグ付きユニオンでも排他的論理和を実現できる。タグを追加できる場合は、こちらが一般的である。

interface ThingOneTag {
  type: "one"
  shirtColor: string
}
interface ThingTwoTag {
  type: "two"
  hairColor: string
}
type Thing = ThingOneTag | ThingTwoTag

キーワード