kurosame’s diary

フロントエンド中心です

TypeScript のユニオン型とインターセクション型について

はじめに

ユニオン型(|)とインターセクション型(&)の違いを簡単な例でおさらいしておこうと思います

以下の型定義を利用して、実装します

type A = {
  a: string
}

type B = {
  b: number
}

type U = A | B
type I = A & B

すべてのプロパティを実装する

const u: U = {
  a: '',
  b: 0
}

const i: I = {
  a: '',
  b: 0
}

上記は当然ですが、uiのどちらも TS エラーは起きません

存在しないプロパティを実装する

cというABのどちらにも存在しないプロパティを設定する

const u: U = {
  a: '',
  b: 0,
  c: false
}

const i: I = {
  a: '',
  b: 0,
  c: false
}

上記はuiどちらもエラーになる
(そりゃそうだ)

ユニオン型で一部のプロパティを実装する

const u: U = {
  a: ''
}

上記はエラーにならない
ユニオン型はABのプロパティのいずれかを実装していれば良い
よって、Aのプロパティ(a)を実装しているので問題ない

const u: U = {}

上記はエラー
ABのプロパティのいずれかを実装できていない

インターセクション型で一部のプロパティを実装する

const i: I = {
  a: ''
}

上記はエラーになる
インターセクション型はABのプロパティのすべてを実装する必要がある
よって、Bのプロパティ(b)を実装する必要がある

const i: I = {}

上記は当然エラー

Aのプロパティをオプショナル型にしてみる

プロパティをオプショナル型にするとどうなるか

type A = {
  a?: string
}

type B = {
  b: number
}

type U = A | B
type I = A & B
const u: U = {}

上記はエラーにならない
ユニオン型はABのプロパティのいずれかを実装していれば良いので、上記はaの undefined が実装できているということになる

const i: I = {}

上記はエラー
インターセクション型の場合は、ABのプロパティのすべてを実装する必要があるので、この場合はbを必ず実装する必要がある
ちなみにbもオプショナル型にすると、上記はエラーになりません

ここまでのまとめ

ここまでの例をまとめると、ユニオン型がABのプロパティのいずれかを実装で、インターセクション型がABのプロパティのすべてを実装と言えます

今度は以下の型定義を例に実装します

type A = {
  a: string
  b: number
}

type B = {
  b: number
  c: string
}

type U = A | B
type I = A & B

const u: U
const i: I

上記のuiの変数は初期値が設定されていないため、コンパイルエラーになっています
初期値は何を与えればよいでしょうか

u{ a: "a", b: 1 }もしくは、{ b: 1, c: "c" }です(値は適当)
i{ a: "a", b: 1, c: "c"}です(値は適当)

つまり、
uABのプロパティのいずれかを実装(bは必須で実装し、acはいずれかが実装されていないといけない)
iABのプロパティのすべてを実装
となります

また、ui内のプロパティを参照する際も、
ubは参照できるが、acは存在するか確定させるまで参照不可となります
つまり、型ガード(a in u)などを使って、acが存在することを確定させないと、参照できません
ia,b,cのすべてのプロパティが参照できます

もし、上記の初期値を設定した上で、以下のようにbの型を異なるものに変えるとどうなるでしょうか

// bがnumber型
type A = {
  a: string
  b: number
}

// bがstring型
type B = {
  b: string
  c: string
}

この場合、
A | Bの時のbnumber | stringとなり、number型かstring型を設定すれば良い
A & Bの場合は、コンパイルエラー(後述のプリミティブ型データの場合を参照)
となります

プリミティブ型データの場合

最後にプリミティブ型の例です

type U = string | number
type I = string & number
const us: U = '' // ok
const ui: U = 0 // ok
const ub: U = false // エラー

const is: I = '' // エラー
const ii: I = 0 // エラー
const ib: I = false // エラー

ユニオン型は、分かりやすいですね
インターセクション型は、string 型と number 型の両方を表現するのは不可能なので、never 型になります
never 型は事前に起こることを想定してはいけない型なので、never 型の変数を定義し、何かを代入することは不可能です
つまり、プリミティブ型データのインターセクション型というものは定義できないということになります