TypeScriptの性質と「&」がもたらす落とし穴
突然ですが…
このコードを見てください。
type Fruit = {
title: string
price: number
}
type WithMetaFields = {
docId: string
createdAt: Date
createdAccountId: string
updatedAt: Date
updatedAccountId: string
}
const apple: Fruit & WithMetaFields = {
title: "リンゴ",
price: 200,
docId: "33",
createdAt: new Date(2025,10,13),
createdAccountId: "taketo",
updatedAt: new Date(2025,10,13),
updatedAccountId: "taketo"
}
function getPrice(fruit: Fruit) {
return fruit.price
}
console.log(getPrice(apple))
さて、出力は次のうちどちらになると思いますか?正解を見てしまった方も、なぜそうなるかを一度考えてみてください。
- 実行前に型エラー
- 200
正解
✅200
💡 Fruit & WithMetaFieldsって「両方を持つ型」じゃないの?
これは理解としてあっています。
インターセクション型 (&) は、2つの型を合成し、両方のプロパティを持つ型を表します。
type A = { x: number }
type B = { y: number }
type AB = A & B
const p: AB = { x: 1, y: 2 } // ✅ OK
そのため、以下のように定数宣言をするとエラーになります。
const ab:AB = { x: 1 } // Compile Error ✖
しかし、getPriceには部分的なFruit型だけでコンパイルエラーが出ない…。
ではなぜ getPrice(fruit: Fruit) に Fruit & WithMetaFields が渡せるのか。
答えは TypeScript の「型の考え方」にあります。
🧩 TypeScriptは「構造的型付け」
TypeScriptは**構造的型付け(structural typing)**の言語です。
つまり、「型の名前」ではなく、「中身の構造」を基準に互換性を判断します。
Fruit に必要なプロパティがすべて存在していれば、
それが Fruit & WithMetaFields だろうと、Animal だろうと、通ってしまうのです。
type Fruit = { title: string; price: number }
type Animal = { title: string; price: number }
const apple: Fruit = { title: "リンゴ", price: 200 }
function getPrice(animal: Animal) {
return animal.price
}
console.log(getPrice(apple)) // ✅ 200
構造が同じなので、異なる型でも代入できてしまう。
これがTypeScriptの柔軟さであり、同時に落とし穴でもあります。
🧱 「柔軟すぎて怖い」を防ぐブランド型
構造的型付けは便利ですが、
「別物のはずなのに同じ構造をしているだけで混ざってしまう」
——そんな問題を避けたいときに使えるのが ブランド型(Brand Pattern) です。
type FruitId = {
id: number
__brand: "FruitId"
}
type AnimalId = {
id: number
__brand: "AnimalId"
}
const fruitId = { id: 1 } as FruitId
const animalId = { id: 1 } as AnimalId
// const test: AnimalId = fruitId ❌ 型エラー!
__brand のような「タグ」を型に埋め込むことで、
名義的(nominal)な区別をつけられます。
つまり、構造が同じでも別の型として扱えるようになります。
__brand:"AnimalId"の型って何?
ちなみにこの__brand:"AnimalId"は、"AnimalId"という文字列リテラルのみを許容する型になります。
📝 まとめ
| 概念 | 内容 |
|---|---|
インターセクション型 (&) |
複数の型を合成して「両方のプロパティ」を持つ |
| 構造的型付け | 「構造が同じなら同じ型」とみなす |
| ブランド型 | 構造が同じでも区別できるようにするデザインパターン |
🍀 おわりに
TypeScriptは柔軟で、少しぐらい型が違っても怒られません。
でもその柔軟さゆえに、「違うけど通る」ケースも多い。
&で型を合成するときは、
「構造的型付けによる暗黙の互換性」が働いていることを意識すると、
より正確な型設計ができるようになります。
この記事の作成
この記事は、要点を自分でまとめる→chatGPTに下書きを作成してもらう→自分で校閲して作成しました。
Discussion