まずイメージでつかむ:unionとintersectionは「または」と「かつ」
最初に一番大事なことだけ頭に入れてください。
A | B(union型)
→ 「A または B」。どちらか一方ならOK。A & B(intersection型)
→ 「A かつ B」。両方とも満たしていないとNG。
つまり、
「どっちかならいいよ」という“選択肢の型”が union
「両方の条件を同時に満たしてね」という“全部乗せの型”が intersection
このイメージをずっと軸にして、例を見ていきます。
具体例その1:オブジェクトで比べると違いが分かりやすい
union型(A または B)
まずは次の2つの型を用意します。
type HasName = {
name: string;
};
type HasAge = {
age: number;
};
TypeScriptこれで union 型を作ります。
type NameOrAge = HasName | HasAge;
TypeScriptNameOrAge は「名前を持つ人」か「年齢を持つ人」のどちらかです。
const a: NameOrAge = { name: "Taro" }; // OK
const b: NameOrAge = { age: 20 }; // OK
const c: NameOrAge = { name: "Taro", age: 20 }; // これもOK(どっちも満たしている)
TypeScriptunion は「どちらか一方を満たしていればOK」なので、
name だけでも age だけでも、両方あっても、全部OKです。
intersection型(A かつ B)
同じ2つの型を、今度は intersection 型で結びます。
type NameAndAge = HasName & HasAge;
TypeScriptこれは「名前も年齢も持っている人」です。
const x: NameAndAge = { name: "Taro", age: 20 }; // OK
const y: NameAndAge = { name: "Taro" }; // エラー(age がない)
const z: NameAndAge = { age: 20 }; // エラー(name がない)
TypeScriptintersection は「両方満たしていないとダメ」なので、
name だけ・age だけでは不十分です。
ここでの違いを言い換えると、
- union:
HasNameかHasAgeのどれかを含む「ゆるい箱」 - intersection:
HasNameとHasAgeを両方含む「厳しい箱」
というイメージです。
具体例その2:関数の引数として考えると違いが光る
union型の引数:「どっちを渡してもいいよ」
type HasId = { id: number };
type HasEmail = { email: string };
type IdOrEmail = HasId | HasEmail;
function findUser(info: IdOrEmail) {
// info が id を持っているか、email を持っているか、ここでは分からない
}
TypeScriptfindUser は、「id だけ」でも「email だけ」でも呼べます。
findUser({ id: 1 }); // OK
findUser({ email: "a@example.com" }); // OK
TypeScript呼び出し側から見ると、「どっちを持っていてもいい」柔らかい関数です。
その代わり、関数の中では「どっちを持っているか分からない」ので、
if 分岐(型ガード)で絞り込んでから使う必要があります。
intersection型の引数:「両方そろってないと呼べない」
同じ2つを intersection で作ります。
type IdAndEmail = HasId & HasEmail;
function saveUser(info: IdAndEmail) {
// info.id も info.email も安全に使える
}
TypeScriptsaveUser を呼ぶには、id も email も両方必要です。
saveUser({ id: 1, email: "a@example.com" }); // OK
saveUser({ id: 1 }); // エラー
saveUser({ email: "a@example.com" }); // エラー
TypeScriptここでは、呼び出し側に対して
「この関数を使いたいなら、id も email もちゃんと用意してね」
という“厳しめの契約”を intersection 型で表現しています。
まとめると、
union 引数 → 「片方あれば呼べる。中ではどっちか分からないから、if で確認が必要」
intersection 引数 → 「両方なければ呼べない。中では何も気にせず両方使える」
という対比になります。
「できること」の違い:unionは制限され、intersectionは増える
union型の値は「共通部分だけ」直接使える
string | number を例にします。
function useUnion(value: string | number) {
// value.toUpperCase(); // エラー
value.toString(); // OK(両方にある)
}
TypeScript理由はこうです。
value が string のときは toUpperCase がある
value が number のときは toUpperCase がない
「どっちか分からない」状態なので、
両方にあるメソッド(toString など)しか許されません。
つまり union 型は「守備範囲が広いぶん、何をしていいかは制限される」 型です。
intersection型の値は「両方のプロパティを持つ」
オブジェクトの intersection は逆に、「できること」が増えます。
type A = { a: string };
type B = { b: number };
type AB = A & B;
function useIntersection(value: AB) {
console.log(value.a); // OK
console.log(value.b); // OK
}
TypeScriptAB は A でもあり B でもあるので、A のプロパティも B のプロパティも両方使えます。
直感的には、
union:型の「候補」は増えるが、1つの値として「安全にできること」は減る
intersection:1つの値に「情報」を盛るので、できることは増える
という関係です。
設計の視点:いつ union を使い、いつ intersection を使うか
unionを選ぶべき場面
「どれか1つのパターンであればよい」というときです。例えば、
ID が文字列のときと数字のとき、両方ありうる
API レスポンスが「成功」か「エラー」のどちらか
状態が “idle” | “loading” | “success” | “error” のどれか
など、「いくつかのパターンのうち1つが現実に起こる」ものを表すのに向いています。
このとき、if / switch で「今どのパターンなのか?」を判定してから
それぞれに応じた処理を書くのが王道パターンです。
intersectionを選ぶべき場面
「この役割のものは、複数の性質を同時に持っている」ときです。例えば、
基本的なユーザー情報(id, name)
+ ログイン中である(token を持つ)
+ 管理者権限を持つ(isAdmin を持つ)
など、「複数の性質を全部持っていて初めて意味を持つ存在」を表すのに向いています。
関数でいうと、
この関数を呼ぶには A の情報も B の情報も必要
このオブジェクトは X としても Y としてもふるまえる
といった場面で intersection を使うと、設計意図がきれいに型に表れます。
最後に:自分の言葉で union と intersection を言い直してみる
ここまでを、あなた自身の言葉でこう言い換えてみてください。
union(|)は
「この値は A のときもあるし、B のときもある。どれか1つでいい。」
intersection(&)は
「この値は A でもあり、同時に B でもある。全部揃っていてほしい。」
そして、コードを書くたびに自分に問うのが大事です。
ここでは「どれか1つでいい」のか?
それとも「全部そろっていてほしい」のか?
その答えが | と & の選択になります。
この「or か and か」を意識しながら型を書くようになると、
TypeScript の型は単なる“おまけ”ではなく、
あなたの頭の中のルールを、そのままコードに刻み込むための言語になっていきます。
