【入門編】Interfaceの構造的部分型と「余剰プロパティチェック」の境界線:なぜ代入時にエラーが出るのか – TypeScript コア・型システムの基礎解析バイブル

TypeScriptの「なぜ?」を解く:Interfaceと型エイリアスの境界線と、魔法の「余剰プロパティチェック」の正体

こんにちは。TypeScriptの世界へようこそ。

現場でバリバリとコードを書いていると、ふとこんな瞬間に遭遇しませんか?
「あれ、このオブジェクト、型定義に合ってるはずなのになぜエラーが出るんだ?」
「変数に一度代入すると通るのに、直接引数に渡すと怒られるのはなぜ?」

実はこれ、TypeScriptが「構造的部分型(Structural Subtyping)」という柔軟なルールを持ちながら、同時に「余剰プロパティチェック(Excess Property Checking)」という厳格なガードレールを敷いているからなんです。

今日は、この「一見矛盾するような挙動」の裏側を、コンパイラの視点から紐解いていきましょう。ここさえ理解すれば、あなたはもうTypeScriptの型システムを怖がる必要はありません。

—

1. まずは「構造的部分型」という考え方を知る

TypeScriptは、JavaやC#のような「名前」で型を判定する(名目的型システム)のではなく、「その中身が条件を満たしているか(構造的型システム)」で判定します。

interface User {
name: string;
}

const person = { name: “Alice”, age: 30 }; // 余計な age がある
const user: User = person; // エラーにならない!

なぜエラーにならないのか?それは「`User`が求める`name`プロパティを、`person`がちゃんと持っているから」です。TypeScriptは「少なくともこれだけあればいいよ」という寛大な心を持っているんですね。これがTypeScriptの強みである柔軟性の正体です。

—

2. なぜ「直接リテラルを渡す」とエラーになるのか?

では、先ほどの例を少し変えてみましょう。

interface User {
name: string;
}

// 直接オブジェクトリテラルを渡すと…?
function printUser(user: User) {
console.log(user.name);
}

printUser({ name: “Alice”, age: 30 });
// 🚨 エラー: ‘age’ は型 ‘User’ に存在しません。

ここで多くの人が混乱します。「さっきは変数に入れたら通ったのに、なぜ直接書くとダメなの?」と。

これこそが「余剰プロパティチェック」です。

脳内トレース:コンパイラの視点

コンパイラは、オブジェクトリテラルが直接渡されたとき、「おっと、これはただの代入じゃないぞ。リテラルが直接渡されたということは、タイポ(打ち間違い)をしている可能性が高いな」と推測します。

  • もし`age`と書くつもりが`agde`と打ち間違えていたら?
  • もし`User`インターフェースに定義されていない不要なプロパティが紛れ込んでいたら?

コンパイラは、あなたの「意図」を深読みして、「定義にないプロパティが含まれているよ。本当にこれでいいの?」と警告を発してくれるのです。これはバグを未然に防ぐための、非常に親切な機能なんですね。

—

3. 回避策:変数を介するか、型アサーションか

このチェックを回避したい、あるいは意図的に構造を広げたい場合はどうすればいいのでしょうか。

① 変数に一度代入する

前述の通り、変数に格納してから渡すとチェックはスキップされます。これはTypeScriptが「変数はすでにその型として評価済みである」と判断するためです。

const newUser = { name: “Alice”, age: 30 };
printUser(newUser); // 通る!

② インターフェースに拡張性を持たせる(インデックスシグネチャ)

「追加のプロパティを許容したい」という設計上の意図があるなら、インデックスシグネチャを使うのが正解です。

interface User {
name: string;
[key: string]: any; // 「文字列のキーがあれば、どんな値が入ってもいいよ」という宣言
}

printUser({ name: “Alice”, age: 30 }); // これならエラーにならない

—

4. 結局、どう使い分けるべき?

  • インターフェース(Interface):

「このオブジェクトは、この形であるべきだ」という契約(Contract)を定義する際に最適です。拡張が容易で、宣言の結合(Declaration Merging)もできるため、ライブラリ開発や大規模開発の基盤に向いています。

  • 型エイリアス(Type Alias):

`type`は複雑な型合成(ユニオン型やインターセクション型)に向いています。基本的にはインターフェースを優先し、必要に応じて`type`を使うのが、現代的なTypeScriptのベストプラクティスです。

—

最後に:TypeScriptはあなたの敵ではない

初心者のうちは、コンパイラの「余剰プロパティチェック」が厳しく感じられるかもしれません。ですが、これはコンパイラが「あなたのコードの意図を汲み取ろうと必死に頑張っている証拠」です。

「なぜ怒られているのか?」を構造的に理解すれば、TypeScriptは単なる制限ツールではなく、あなたの開発を強力にバックアップしてくれる最高の相棒に変わります。

ここをクリアしたあなたは、もうTypeScriptの型システムの基本をマスターしたも同然です。ぜひ、自信を持ってコードを書き進めてくださいね!

—
何か不明な点や、「もっと深く知りたい!」というトピックがあれば、いつでも教えてください。また次の記事でお会いしましょう!

タイトルとURLをコピーしました