Menu

TypeScriptの交差型(&): 型を組み合わせる

交差型 A & B は、同時に A でも B でもある値を表すので、両方のメンバーをすべて持ちます。& でオブジェクト型を組み合わせる方法、衝突するプロパティが never になる理由、ユニオン型の交差型が共通のメンバーだけを残すこと、代わりに extends を使うべき場面を解説します。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

A & B と書く交差型は、A であると同時に B でもある値を表します。オブジェクト型なら、値は両方のプロパティをすべて持つということです。プロパティを書き直さずに既存の型を組み合わせる方法です。

2つ目のオブジェクトは team を省いているので、コンパイルエラー TS2322 になり、メッセージの次の行には Property 'team' is missing ... but required in type 'Employee' と書かれています。Staff の値は、Person や Employee が期待される場所ならどこにでも渡せます。

オブジェクト型を組み合わせる

& は、型エイリアス、インターフェース、インラインのオブジェクト型をどう混ぜても使え、ジェネリクスの型パラメーターにも使えます。置き換えが難しいのはこの最後のケースです。受け取ったオブジェクトが何であれプロパティを追加する関数を、正確に表現できます。

呼び出し側は、渡したものの正確な型(title、words)に、追加された2つのプロパティを加えた型を受け取ります。インターフェースは型パラメーターを拡張できないので、interface ... extends では「T が何であれ、それにこれらを加えたもの」を表現できません。

衝突するプロパティは never になる

両側が同じプロパティを宣言していると、そのプロパティの型は2つの型の交差型になります。その2つの型に共通の値がなければ、プロパティは never になり、値を作ろうとするまでコンパイラーは何も言いません:

index.ts(7,22): error TS2322: Type 'string' is not assignable to type 'never'.

エラーは原因となった型ではなくオブジェクトを指すので、この種のバグは原因をたどるのに時間がかかります。エディターで r.id にカーソルを合わせると、型が never だとわかります。type Shape = { kind: "circle" } & { kind: "square" } のように衝突するプロパティがリテラルのタグなら、TypeScriptはさらに進んで交差型全体を never に縮約します。そうした値のプロパティを読むと、理由が説明されます: Property 'kind' does not exist on type 'never'. The intersection 'Shape' was reduced to 'never' because property 'kind' has conflicting types in some constituents.

interface ... extends なら、同じ衝突を宣言の位置でエラー TS2430 として捕まえます。これが2つの主な実用上の違いで、interface と type のページで並べて比較しています。

重なりがあればプロパティが狭まる

2つのプロパティの型に重なりがあれば、結果はその重なりになります。これはエラーではなく、便利な性質です:

後半は、& がユニオン型に何をするかを示しています。両側に共通するメンバーを残します。型を値の集合として考えると、これは予想どおりの結果です。A | B は2つの集合の和集合、A & B はその共通部分で、何もない共通部分が never です。

交差型とユニオン型

名前は集合論から来ていて、オブジェクトのプロパティに当てはめると逆に感じられます:

A | B(ユニオン型)A & B(交差型)
値はA または BA かつ B
許される値の集合大きい小さい
使えるプロパティ両方にあるものだけどちらかにあるものすべて
string と numberstring | numbernever
"a" | "b" と "b" | "c""a" | "b" | "c""b"

オブジェクト型の交差型がより多くのプロパティを持つのは、まさに許す値が少ないから、つまりすべてを持つオブジェクトだけを許すからです。

交差型と extends

type C = A & Binterface C extends A, B
対象ユニオン型や型パラメーターを含むあらゆる型メンバーが静的にわかるオブジェクト型
衝突するプロパティ黙って never になる宣言の位置でエラー TS2430 または TS2320
結果構成要素ごとにチェックされる交差型関係がキャッシュされる、1つのフラットな名前付きの型
大きな組み合わせ型チェックが遅くなることがあるTypeScript wiki の Performance のページが推奨

いくつかのオブジェクト型のエイリアスを組み合わせるなら、& が慣用的で問題ありません。多くの部品から作る型や公開APIの型なら、extends のほうがエラーが早く出て、型チェックも軽くなります。

プリミティブとの交差型: ブランド化

プリミティブとオブジェクト型の交差型は never になりません。string & { readonly __brand: "UserId" } は、コンパイル時だけの目印を追加で持つ文字列です。本物の文字列はそのプロパティを持たず、それこそが狙いです。ブランドを意図的にアサーションするコードだけがそれを作れるので、UserId が期待される場所に普通の string や OrderId を渡せなくなります。この手法には専用のページがあります: ブランド型。

よくある質問

TypeScriptの交差型とは何ですか?

A & B と書く型で、その値は A と B の両方を同時に満たさなければなりません。オブジェクト型なら、値は A のすべてのプロパティと B のすべてのプロパティを持つということです。2つの型エイリアスを1つに組み合わせる一般的な方法です。

ユニオン型と交差型の違いは何ですか?

ユニオン型 A | B は「どちらか一方」を意味します。値は A か B で、絞り込むまでは共通するものしか使えません。交差型 A & B は「両方」を意味し、値は両方のすべてを持ちます。オブジェクト型では、ユニオン型はより多くの値を受け付け、交差型はより多くのプロパティを持ちます。

交差型が never になってしまうのはなぜですか?

両側を満たせる値がないからです。string & number は never で、{ id: string } & { id: number } では id が string & number になるので、プロパティは never になり、オブジェクトを作れません。2つのオブジェクト型が同じリテラルのタグを違う値で持つ場合(kind: "circle" と kind: "square")、交差型全体が never に縮約されます。

交差型と extends のどちらを使うべきですか?

どちらもオブジェクト型を組み合わせます。interface X extends A, B は衝突するプロパティを宣言の位置で報告し、大きなオブジェクト型を組み立てるときにTypeScriptチームが推奨しています。& は、extends では組み合わせられないユニオン型やジェネリクスの型パラメーターを含め、どんな型にも使えます。型エイリアスやジェネリックなヘルパーには & を、インターフェースを作るときは extends を使いましょう。

TypeScriptで2つのオブジェクト型をマージするには?

type Merged = A & B と書きます。実行時の値は、両方のオブジェクトをスプレッドします: const merged: A & B = { ...a, ...b }。A と B が違う型の同じプロパティを持っていると、そのプロパティの型は never になります。2つ目のオブジェクトのプロパティで1つ目を置き換えたいなら、Omit<A, keyof B> & B を使います。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める