- TypeScript
TypeScriptユニオン型と交差型の違い||と&の使い方と絞り込み早見表
ユニオン型(A | B)は「AかBのどちらか」、交差型(A & B)は「AでありBでもある」を表す型です。 ユニオン型は受け取れる値の範囲を広げ、交差型は複数の型の条件をすべて満たすものに絞ります。
この記事では、2つの違いを 「代入できる値・使えるプロパティ・プリミティブ同士の結果・主な用途」の早見表 で並べ、絞り込みの方法5種類と、switch の default で never に代入する網羅チェックを扱います。エラー文はすべて TypeScript 7.0.2(strict: true)の tsc --noEmit で実際に出力したもの、実行結果は Node.js 22.22 で動かしたものです。
ユニオン型と交差型の早見表
| 比べる点 | ユニオン型 A | B |
交差型 A & B |
|---|---|---|
| 読み方 | AまたはB(どちらか) | AかつB(両方) |
| 記号 | 縦棒 | |
アンパサンド & |
| 代入できる値 | AかBのどちらかを満たす値 | AとBの両方を満たす値 |
| オブジェクト型で代入できる値 | Aだけ・Bだけ・両方のプロパティを持つ値 | AとBのプロパティをすべて持つ値 |
| そのまま使えるプロパティ | AとBの両方にあるものだけ | AとBのどちらかにあるものすべて |
| プリミティブ同士(string と number) | string | number(どちらも入る) |
never(入る値がない) |
| 同名プロパティの型が違うとき | アクセスするとそれぞれの型のユニオン | そのプロパティが never になる |
| 使う前に必要なこと | 絞り込み(typeof・in など) | 特になし(そのまま使える) |
| 主な用途 | 複数パターンの値、状態の分岐、null を許す | 型の合成、共通項目の追加、既存の型の拡張 |
| つまずきやすいエラー | TS2339(プロパティが存在しない) | TS2322(never に代入できない) |
迷ったら「値のパターンが分かれるならユニオン型、項目を足し合わせるなら交差型」 と考えると選びやすくなります。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「TypeScriptユニオン型と交差型の違い||と&の使い方と絞り込み早見表」
検証環境と試し方
記事のコードは、次の手順でそのまま試せます。
mkdir ts-union && cd ts-union
npm init -y
npm i typescript tsx
npx tsc -v
Version 7.0.2
tsconfig.json は次の内容です。
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"strict": true,
"noEmit": true,
"lib": ["es2023", "dom"],
"types": []
},
"include": ["*.ts"]
}
型チェックは npx tsc --noEmit --pretty false、実行は npx tsx ファイル名.ts で行いました。
ユニオン型(|)の基本:どちらかを受け取る
ユニオン型は、型を縦棒でつないで書きます。「この変数には文字列か数値のどちらかが入る」 のように、複数の可能性を1つの型で表せます。
type Id = string | number; // ユニオン型:どちらか
const a: Id = "A-1"; // OK
const b: Id = 42; // OK
const c: Id = true; // エラー
basic.ts(8,7): error TS2322: Type 'boolean' is not assignable to type 'Id'.
よく使うユニオン型の形
| 形 | 書き方 | 意味 |
|---|---|---|
| プリミティブ同士 | string | number |
文字列か数値 |
| null を許す | string | null |
文字列か null |
| 省略可能 | string | undefined |
文字列か未設定 |
| リテラル型のユニオン | "todo" | "doing" | "done" |
決まった文字列のどれか |
| オブジェクト型のユニオン | Member | Guest |
どちらかの形のオブジェクト |
| 配列の要素がユニオン | (string | number)[] |
文字列と数値が混ざる配列 |
配列と組み合わせるときは、かっこの位置で意味が変わります。
const list: string | number[] = "a"; // 「文字列」か「数値の配列」
const list2: (string | number)[] = ["a", 1]; // 「文字列か数値」の配列
ユニオン型でそのまま使えるのは共通のプロパティだけ
ユニオン型の値は「どちらなのかまだわからない」状態です。そのため、両方の型にあるプロパティだけがそのまま使えます。
type Cat = { name: string; meow: () => string };
type Dog = { name: string; bark: () => string };
function union(pet: Cat | Dog) {
pet.name; // OK:両方にある
pet.meow(); // エラー
}
function len(v: string | number) {
return v.length; // エラー
}
prop.ts(6,7): error TS2339: Property 'meow' does not exist on type 'Cat | Dog'.
Property 'meow' does not exist on type 'Dog'.
prop.ts(16,12): error TS2339: Property 'length' does not exist on type 'string | number'.
Property 'length' does not exist on type 'number'.
TS2339 の2行目が手がかりです。 「Dog には meow がない」「number には length がない」と、どちらの型で足りないのかを教えてくれます。この後で扱う「絞り込み」で型を1つに決めれば、エラーは解消します。
交差型(&)の基本:両方を満たす
交差型は、型をアンパサンドでつないで書きます。オブジェクト型同士なら「両方のプロパティをすべて持つ型」 になります。
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged; // 交差型:両方
const p1: Person = { name: "sato", age: 30 }; // OK
const p2: Person = { name: "sato" }; // エラー
basic.ts(11,7): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
Property 'age' is missing in type '{ name: string; }' but required in type 'Aged'.
交差型の値は両方の条件を満たしているので、絞り込みをしなくてもすべてのプロパティが使えます。
function intersection(pet: Cat & Dog) {
pet.name; // OK
pet.meow(); // OK
pet.bark(); // OK
}
同じ2つの型で、ユニオン型と交差型を比べる
Named と Aged を、ユニオン型と交差型のそれぞれでつないだ結果です。
type Either = Named | Aged;
const e1: Either = { name: "sato" }; // OK
const e2: Either = { age: 30 }; // OK
const e3: Either = { name: "sato", age: 30 }; // OK(両方持っていてもよい)
const e4: Either = {}; // エラー
basic.ts(17,7): error TS2322: Type '{}' is not assignable to type 'Either'.
| 代入する値 | Named | Aged |
Named & Aged |
|---|---|---|
{ name: "sato" } |
OK | エラー(age が足りない) |
{ age: 30 } |
OK | エラー(name が足りない) |
{ name: "sato", age: 30 } |
OK | OK |
{} |
エラー | エラー |
ユニオン型は「どちらか一方だけ」という意味ではなく、「少なくともどちらかを満たす」 という意味です。両方のプロパティを持つ値も代入できます。
「和」と「積」が逆に感じるときの考え方
「ユニオン(和)なのに使えるプロパティは減り、交差(積)なのに増える」と感じることがあります。これは、型を「値の集まり」として見るか「プロパティの集まり」として見るかの違いです。
| 見方 | ユニオン型 | 交差型 |
|---|---|---|
| 入れられる値の範囲 | 広がる(AもBも入る) | 狭まる(両方を満たす値だけ) |
| 確実に使えるプロパティ | 減る(共通部分だけ) | 増える(すべて) |
keyof で確かめると、この関係がはっきりします。
type Named = { name: string; id: number };
type Aged = { age: number; id: number };
type KU = keyof (Named | Aged); // "id"
type KI = keyof (Named & Aged); // "name" | "id" | "age"
const ku: KU = "name"; // エラー
const ki: KI = "name"; // OK
misc.ts(17,7): error TS2322: Type '"name"' is not assignable to type '"id"'.
ユニオン型のキーは共通の id だけ、交差型のキーは3つすべてになりました。
プリミティブ同士の&はneverになる
string と number を交差型でつなぐと、「文字列であり、かつ数値でもある値」という意味になります。そのような値は存在しないので、結果は never(どんな値も入らない型) になります。
type A = string & number; // never
type B = "a" & "b"; // never
type C = string & "a"; // "a"
type D = (string | number) & (number | boolean); // number
type F = { kind: "a" } & { kind: "b" }; // never
const a: A = "x";
const d: D = 1; // OK
const d2: D = "x";
const f: F = { kind: "a" };
never1.ts(8,7): error TS2322: Type '"x"' is not assignable to type 'never'.
never1.ts(10,7): error TS2322: Type 'string' is not assignable to type 'number'.
never1.ts(11,16): error TS2322: Type 'string' is not assignable to type 'never'.
| 交差型 | 結果 | 理由 |
|---|---|---|
string & number |
never | 両方を満たす値がない |
"a" & "b" |
never | 同時に2つの文字列にはなれない |
string & "a" |
"a" |
「文字列で、かつ "a"」は "a" |
(string | number) & (number | boolean) |
number | 両方のユニオンに共通するのは number |
{ kind: "a" } & { kind: "b" } |
never | kind が両方を満たせない |
D の例は、ユニオン型同士の交差が「共通部分を取り出す」働きをすることを示しています。2つ目のエラーで「number に代入できない」と表示されているとおり、D は number として扱われています。
オブジェクト型の&でプロパティが衝突する例
交差型でつないだ2つの型に 同じ名前で型の違うプロパティ があると、そのプロパティ自体が交差型になります。
type Base = { id: number; tags: string[]; meta: { createdBy: string } };
type Extra = { id: string; tags: number[]; meta: { updatedBy: string } };
type Both = Base & Extra;
const x: Both = {
id: 1,
tags: [],
meta: { createdBy: "sato", updatedBy: "suzuki" },
};
conflict.ts(10,3): error TS2322: Type 'number' is not assignable to type 'never'.
| プロパティ | Base | Extra | Both での型 | 値を入れられるか |
|---|---|---|---|---|
| id | number | string | number & string(never) |
入れられない |
| tags | string[] |
number[] |
string[] & number[] |
空の配列なら通った |
| meta | { createdBy: string } |
{ updatedBy: string } |
両方のプロパティを持つ型 | 入れられる |
型の定義そのものはエラーにならず、値を代入しようとしたときに初めて never のエラーが出る点が、気づきにくいところです。meta のようにオブジェクト型同士なら、入れ子の中でも合成されます。
interface の extends なら定義の時点でわかる
同じ衝突を interface の extends で書くと、定義した時点でエラーになります。
interface Base { id: number }
interface Extra extends Base { id: string }
iface.ts(2,11): error TS2430: Interface 'Extra' incorrectly extends interface 'Base'.
Types of property 'id' are incompatible.
Type 'string' is not assignable to type 'number'.
| やりたいこと | 向いている書き方 |
|---|---|
| オブジェクト型に項目を足す(衝突を早く見つけたい) | interface の extends |
| ユニオン型やユーティリティ型と組み合わせて合成する | type の交差型 |
| 既存の型の一部を別の型に差し替える | Omit で外してから交差型で足す |
プロパティの型を差し替えたいときは、先に Omit で取り除いてから足します。
type Base = { id: number; name: string };
type WithStringId = Omit<Base, "id"> & { id: string };
const v: WithStringId = { id: "A-1", name: "sato" }; // OK
絞り込み方法の一覧
ユニオン型の値は、条件分岐で型を1つに決めてから使います。 これを絞り込み(ナローイング)と呼びます。
| 方法 | 書き方 | 向いている対象 |
|---|---|---|
| typeof | typeof v === "string" |
string・number・boolean などのプリミティブ |
| in | "point" in u |
プロパティの有無で見分けられるオブジェクト |
| instanceof | v instanceof Date |
クラスのインスタンス(Date・Error・自作クラス) |
| 判別プロパティ | t.status === "todo" |
共通のリテラル型プロパティを持つオブジェクト |
| 型述語 | function isMember(u): u is Member |
判定を関数にまとめて使い回したいとき |
| 等価比較・Array.isArray | v == null、Array.isArray(v) |
null・undefined の除外、配列の判定 |
以下は、6つの方法をまとめたコードです。このまま narrow.ts として保存して実行できます。
// 1. typeof:プリミティブを見分ける
function format(v: string | number | boolean): string {
if (typeof v === "string") return v.toUpperCase(); // v: string
if (typeof v === "number") return v.toFixed(1); // v: number
return v ? "はい" : "いいえ"; // v: boolean
}
// 2. in:プロパティの有無で見分ける
type Member = { name: string; point: number };
type Guest = { name: string; expiresAt: string };
function describe(u: Member | Guest): string {
if ("point" in u) return `${u.name}(会員・${u.point}pt)`; // u: Member
return `${u.name}(ゲスト・${u.expiresAt}まで)`; // u: Guest
}
// 3. instanceof:クラスのインスタンスを見分ける
function toText(v: Date | Error | string[]): string {
if (v instanceof Date) return v.toISOString().slice(0, 10); // v: Date
if (v instanceof Error) return v.message; // v: Error
return v.join(","); // v: string[]
}
// 4. 判別プロパティ:共通のリテラル型プロパティで見分ける
type Task =
| { status: "todo"; title: string }
| { status: "doing"; title: string; startedAt: string }
| { status: "done"; title: string; finishedAt: string };
function taskLine(t: Task): string {
switch (t.status) {
case "todo": return `未着手: ${t.title}`;
case "doing": return `作業中: ${t.title}(${t.startedAt}〜)`;
case "done": return `完了: ${t.title}(${t.finishedAt})`;
}
}
// 5. 型述語(ユーザー定義型ガード):判定を関数にまとめる
function isMember(u: Member | Guest): u is Member {
return "point" in u;
}
const users: (Member | Guest)[] = [
{ name: "sato", point: 120 },
{ name: "suzuki", expiresAt: "2026-12-31" },
];
const members = users.filter(isMember); // Member[]
// 6. 等価比較・Array.isArray
function size(v: string | string[] | null | undefined): number {
if (v == null) return 0; // null と undefined をまとめて除外
if (Array.isArray(v)) return v.length; // v: string[]
return v.length; // v: string
}
console.log(format("abc"), format(3.14159), format(true));
console.log(describe(users[0]!));
console.log(describe(users[1]!));
console.log(toText(new Date("2026-10-03T00:00:00Z")), toText(new Error("見つかりません")), toText(["a", "b"]));
console.log(taskLine({ status: "doing", title: "資料作成", startedAt: "10:00" }));
console.log(members.map((m) => m.point));
console.log(size(null), size("abc"), size(["a", "b"]));
実行結果(npx tsx narrow.ts。型チェックもエラーなし):
ABC 3.1 はい
sato(会員・120pt)
suzuki(ゲスト・2026-12-31まで)
2026-10-03 見つかりません a,b
作業中: 資料作成(10:00〜)
[ 120 ]
0 3 2
どの絞り込み方法を選ぶか
| 状況 | 選ぶ方法 |
|---|---|
| string か number かを見分けたい | typeof |
| null や undefined を除きたい | v == null や if (!v)、オプショナルチェーン |
| 自分で型を設計できるオブジェクト | 判別プロパティ(次の節) |
| 外部から来る、形を変えられないオブジェクト | in |
| クラスで作った値 | instanceof |
| 同じ判定を何度も書く、filter で使いたい | 型述語 |
typeof で見分けられるのはプリミティブと関数までです。オブジェクト・配列・null はどれも "object" になるため、オブジェクト同士を見分けるときは in や判別プロパティを使います。
判別可能なユニオン型:共通のプロパティで見分ける
判別可能なユニオン型(Discriminated Union)は、すべてのメンバーが共通の名前のプロパティを持ち、その値がリテラル型でそれぞれ違うユニオン型です。このプロパティを判別プロパティ(ディスクリミネータ)と呼びます。
判別プロパティがないと絞り込みにくい
まず、判別プロパティを持たない形です。
type Loading = { loading: boolean };
type Ok = { data: string[] };
type Ng = { error: string };
type State = Loading | Ok | Ng;
function show(s: State) {
if (s.data) {
return s.data.length;
}
}
nondisc.ts(7,9): error TS2339: Property 'data' does not exist on type 'State'.
Property 'data' does not exist on type 'Loading'.
nondisc.ts(8,14): error TS2339: Property 'data' does not exist on type 'State'.
Property 'data' does not exist on type 'Loading'.
共通ではないプロパティは、存在を確かめる目的であっても直接は読めません。 in を使えば書けますが、メンバーが増えるほど条件が込み入ってきます。
判別プロパティを付けた形
type Result =
| { ok: true; value: number }
| { ok: false; error: string };
function show2(r: Result) {
const { ok } = r;
if (ok) return r.value; // r: { ok: true; value: number }
return r.error; // r: { ok: false; error: string }
}
function show3(r: Result) {
return r.value; // 絞り込み前なのでエラー
}
nondisc.ts(22,12): error TS2339: Property 'value' does not exist on type 'Result'.
Property 'value' does not exist on type '{ ok: false; error: string; }'.
show2 はエラーになりません。判別プロパティを分割代入で変数 ok に取り出してから判定しても、元の r が絞り込まれます。 判別プロパティには、文字列リテラルのほか true / false や数値リテラルも使えます。
| 判別プロパティの型 | 例 | 向いている場面 |
|---|---|---|
| 文字列リテラル | kind: "circle"、status: "done" |
3種類以上の分岐。名前で意味が伝わる |
| 真偽値リテラル | ok: true と ok: false |
成功とそれ以外の2分岐 |
| 数値リテラル | code: 200 と code: 404 |
番号で種類が決まっているとき |
neverで網羅チェック:分岐の書き漏れをコンパイルで見つける
ユニオン型にメンバーを足したとき、対応する分岐を書き忘れた箇所をコンパイルエラーで教えてもらう書き方です。switch の default で、絞り込まれた残りを never 型の変数に代入します。
エラーを再現する
Shape に Triangle を追加したのに、area 関数の case を増やしていない状態です。
type Circle = { kind: "circle"; radius: number };
type Square = { kind: "square"; size: number };
type Triangle = { kind: "triangle"; base: number; height: number };
type Shape = Circle | Square | Triangle;
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.size ** 2;
default: {
const _exhaustive: never = shape;
throw new Error(`未対応の図形: ${JSON.stringify(_exhaustive)}`);
}
}
}
function label(shape: Shape): string {
if (shape.kind === "circle") {
return `半径 ${shape.radius}`;
}
return `一辺 ${shape.size}`;
}
exhaust.ts(13,13): error TS2322: Type 'Triangle' is not assignable to type 'never'.
exhaust.ts(23,22): error TS2339: Property 'size' does not exist on type 'Square | Triangle'.
Property 'size' does not exist on type 'Triangle'.
1つ目の TS2322 が網羅チェックのエラーです。case で circle と square を処理したあと、default に届く shape の型は残りの Triangle になります。すべて処理済みなら残りは never になるはずなので、never に代入できない=まだ処理していない型がある、とわかります。
2つ目の TS2339 は、if と else で書いた label 関数のものです。circle を除いた残りが Square | Triangle になり、size がそのままでは読めなくなりました。メンバーを足したことで、既存のコードのどこを直せばよいかがエラーの一覧として出てきます。
直したあと
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.size ** 2;
case "triangle":
return (shape.base * shape.height) / 2;
default: {
const _exhaustive: never = shape; // ここに届く型は never
throw new Error(`未対応の図形: ${JSON.stringify(_exhaustive)}`);
}
}
}
網羅チェックの書き方3種類
| 書き方 | 書き漏れたときのエラー | 特徴 |
|---|---|---|
| default で never の変数に代入 | TS2322 | その場に書ける。追加の関数が不要 |
| assertNever 関数に渡す | TS2345 | 複数の switch で使い回せる |
| default を書かず、戻り値の型を明示 | TS2366 | 最も短い。実行時の想定外の値は拾えない |
assertNever 関数を使う形と、default を書かない形で、同じ書き漏れを再現した結果です。
function assertNever(value: never): never {
throw new Error(`未対応の値: ${JSON.stringify(value)}`);
}
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.size ** 2;
default:
return assertNever(shape);
}
}
function noDefault(shape: Shape): number {
switch (shape.kind) {
case "circle":
return 1;
case "square":
return 2;
}
}
exhaust2.ts(17,26): error TS2345: Argument of type 'Triangle' is not assignable to parameter of type 'never'.
exhaust2.ts(26,35): error TS2366: Function lacks ending return statement and return type does not include 'undefined'.
TS2345 と TS2322 は、どの型が残っているか(Triangle)を名前で教えてくれます。TS2366 は「return がない経路がある」という伝え方になります。
オブジェクトの対応表も網羅チェックできる
分岐ではなく「種類ごとの表示名」のような対応表を作るときは、satisfies と Record を組み合わせると、キーの書き漏れを見つけられます。
const names = {
circle: "円",
square: "正方形",
} satisfies Record<Shape["kind"], string>;
exhaust2.ts(24,3): error TS2741: Property 'triangle' is missing in type '{ circle: string; square: string; }' but required in type 'Record<"circle" | "square" | "triangle", string>'.
default の throw は実行時にも役立つ
型の上では届かないはずの default にも、外部から来たデータなら届くことがあります。次のコードは型チェックを通りますが、JSON.parse の戻り値は any なので、型にない kind を持つ値が渡せてしまいます。
type Circle = { kind: "circle"; radius: number };
type Square = { kind: "square"; size: number };
type Shape = Circle | Square;
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.size ** 2;
default: {
const _exhaustive: never = shape;
throw new Error(`未対応の図形: ${JSON.stringify(_exhaustive)}`);
}
}
}
console.log(area({ kind: "square", size: 3 }));
const fromApi = JSON.parse('{"kind":"triangle","base":4,"height":3}');
try {
console.log(area(fromApi));
} catch (e) {
console.log((e as Error).message);
}
実行結果:
9
未対応の図形: {"kind":"triangle","base":4,"height":3}
default で例外を投げておくと、想定していない値が来たことにすぐ気づけます。 default を省く書き方では、この場合に undefined が返って先の処理へ進みます。
ユニオン型と交差型を組み合わせる
2つは組み合わせて使うことが多くあります。「共通の項目は交差型で足し、パターンの違いはユニオン型で分ける」 のが定番の形です。
// 交差型で共通項目を足し、ユニオン型で分岐させる
type Timestamps = { createdAt: string; updatedAt: string };
type WithId = { id: number };
type Article = WithId & Timestamps & { title: string };
type ApiResult<T> =
| { ok: true; data: T }
| { ok: false; error: { code: number; message: string } };
function render(result: ApiResult<Article>): string {
if (result.ok) {
return `#${result.data.id} ${result.data.title}(${result.data.updatedAt})`;
}
return `エラー ${result.error.code}: ${result.error.message}`;
}
const article: Article = {
id: 1,
title: "はじめての記事",
createdAt: "2026-10-01",
updatedAt: "2026-10-03",
};
console.log(render({ ok: true, data: article }));
console.log(render({ ok: false, error: { code: 404, message: "記事が見つかりません" } }));
// ユニオン型に共通項目を & で足す
type Event =
| { type: "click"; x: number; y: number }
| { type: "key"; key: string };
type LoggedEvent = Event & { at: string };
const ev: LoggedEvent = { type: "key", key: "Enter", at: "12:00" };
if (ev.type === "key") console.log(ev.key, ev.at);
実行結果:
#1 はじめての記事(2026-10-03)
エラー 404: 記事が見つかりません
Enter 12:00
LoggedEvent のように ユニオン型に交差型で項目を足すと、各メンバーに同じ項目が配られます。 足したあとも判別プロパティ type による絞り込みはそのまま使えます。
つまずきやすいポイント
関数型のユニオンと交差は、引数の扱いが逆になる
type F1 = (x: string) => string;
type F2 = (x: number) => number;
declare const f: F1 & F2;
const r1 = f("a"); // OK(string が返る)
const r2 = f(1); // OK(number が返る)
declare const g: F1 | F2;
g("a"); // エラー
misc.ts(11,3): error TS2345: Argument of type '"a"' is not assignable to parameter of type 'never'.
関数の交差型は「どちらの呼び方もできる関数」(オーバーロードに近い形)になります。関数のユニオン型は「どちらの関数かわからない」ので、両方の引数の型を同時に満たす値しか渡せません。string と number を同時に満たす値はないため、引数の型が never になります。
オブジェクト型のユニオンでも、書き間違いは見つけてくれる
type Named = { name: string; id: number };
type Aged = { age: number; id: number };
type Either = Named | Aged;
const e: Either = { name: "a", id: 1, agee: 3 };
misc.ts(21,39): error TS2353: Object literal may only specify known properties, and 'agee' does not exist in type 'Either'.
ユニオン型のどのメンバーにもないプロパティをオブジェクトリテラルに書くと、TS2353 で知らせてくれます。
省略可能な引数は undefined とのユニオン型
function up(s?: string) {
return s.toUpperCase();
}
misc.ts(24,34): error TS18048: 's' is possibly 'undefined'.
s?: string は string | undefined として扱われます。s?.toUpperCase() や if (s) で undefined を除いてから使います。
エラーコード別の見直しポイント
| エラー | 出る場面 | 見直すこと |
|---|---|---|
| TS2339 | ユニオン型で共通でないプロパティを読んだ | 絞り込みを入れる。2行目でどの型に無いかを確認 |
| TS2322(never) | 網羅チェックで未処理の型が残っている | 表示された型の case を足す |
| TS2322(never・代入時) | 交差型でプロパティの型が衝突している | Omit で外してから足す、または extends で定義 |
| TS2345(never) | assertNever に未処理の型が届いた/関数のユニオンを呼んだ | case を足す/関数を絞り込んでから呼ぶ |
| TS2366 | switch に書き漏れがあり return が無い経路がある | case を足す |
| TS18048 | undefined を含むユニオン型をそのまま使った | ?. や if で除く |
よくある質問
Q. ユニオン型と交差型は、どちらを使えばよいですか? A. 値のパターンが複数あるならユニオン型、複数の型の項目をまとめて1つにしたいなら交差型です。たとえば「成功か、そうでないか」はユニオン型、「id と日時を全部のデータ型に足したい」は交差型が合います。組み合わせて使うことも多くあります。
Q. 交差型と interface の extends はどう違いますか? A. オブジェクト型に項目を足す用途ではほぼ同じ結果になります。違いは同名プロパティの型が食い違ったときで、extends は定義の時点で TS2430 のエラーになり、交差型はそのプロパティが never になって値の代入時にエラーになります。
Q. 判別プロパティの名前に決まりはありますか? A. 決まりはありません。kind・type・status などがよく使われます。大切なのは、すべてのメンバーに同じ名前で持たせることと、値をリテラル型("circle" や true など)にすることです。string のような広い型にすると絞り込みに使えなくなります。
Q. string & number が never になるなら、交差型はプリミティブには使えないのですか?
A. string & "a" のように、片方がもう片方に含まれる場合は狭いほうの型になります。また、ユニオン型同士の交差は共通部分を取り出す働きをします(今回の例では (string | number) & (number | boolean) が number になりました)。まったく重ならないプリミティブ同士のときに never になります。
まとめ
- ユニオン型(|)は「どちらか」、交差型(&)は「両方」。値の範囲はユニオン型で広がり、交差型で狭まる
- ユニオン型でそのまま使えるのは共通のプロパティだけ。それ以外を読むと TS2339 になるので、絞り込んでから使う
- 絞り込みは typeof・in・instanceof・判別プロパティ・型述語を、対象に合わせて選ぶ
- 判別可能なユニオン型は、共通のリテラル型プロパティを持たせる設計。switch と相性がよい
- default で never に代入すると網羅チェックになり、分岐の書き漏れが TS2322 として表示される
- string & number は never。オブジェクト型の交差で同名プロパティの型が食い違うと、そのプロパティが never になる
- 共通項目は交差型で足し、パターンの違いはユニオン型で分けると、2つを無理なく組み合わせられる
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「TypeScriptユニオン型と交差型の違い||と&の使い方と絞り込み早見表」
株式会社RJCでは、TypeScriptのユニオン型と交差型をはじめとする型の考え方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。