🏡

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))

さて、出力は次のうちどちらになると思いますか?正解を見てしまった方も、なぜそうなるかを一度考えてみてください。

  1. 実行前に型エラー
  2. 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