TypeScript | 基礎文法:オブジェクト基礎 – 部分一致が通らない理由

TypeScript
スポンサーリンク

「部分一致が通らない」ってどういうことか

まず、ここでいう「部分一致」はだいたいこんな状況を指しています。

type User = {
  name: string;
  age: number;
};

const p = {
  name: "Taro",
};

const u: User = p; // これはエラーになってほしい気もするし、ならない気もする…
TypeScript

あるいは逆に、

type User = {
  name: string;
};

const p = {
  name: "Taro",
  age: 20,
};

const u: User = p; // これは通る?通らない?
TypeScript

TypeScript を触り始めると、

「型の一部だけ合っていれば代入できそうなのに、エラーになる」
「逆に、余計なプロパティがあっても代入できてしまう」

みたいな場面に出会って、「部分一致ってどう扱われてるの?」とモヤっとしがちです。
ここを整理するには、TypeScript が持っている二つの視点——「構造的な互換性」と「余剰プロパティチェック」を分けて見るのが近道です。


TypeScriptの基本ルール:「必要なものが全部あればOK」

構造的型システムという考え方

TypeScript は「構造的型システム」です。
ざっくり言うと、「その型が“どんなプロパティを持っているか”で互換性を判断する」というルールです。

type User = {
  name: string;
};

const p = {
  name: "Taro",
  age: 20,
};

const u: User = p; // これは OK
TypeScript

ここで TypeScript はこう考えています。

User 型として必要なのは name: string だけ。
pname: string を持っているから、User として扱っても問題ない。
age が余計にあっても、User として見るときには無視すればいい。」

つまり、「必要なプロパティが全部揃っていれば、余計なものがあっても代入OK」というのが基本ルールです。
この意味では、「部分一致」は「必要な側から見ての部分一致」であれば通る、ということになります。


じゃあなぜ「部分一致が通らない」ように見えるのか

直接オブジェクトリテラルを書いたときだけ、ルールが厳しくなる

さっきの例を、こう書き換えてみます。

type User = {
  name: string;
};

const u: User = {
  name: "Taro",
  age: 20,
};
TypeScript

これはエラーになります。

「さっきは age があっても通ったのに、今度はダメなの?」と感じるところですが、
ここで効いているのが「excess property check(余剰プロパティチェック)」です。

TypeScript は、「オブジェクトリテラルをその場で書いているときだけ」特別に厳しく見ます。

User には age なんてプロパティはない。
それをわざわざここで書いているのは、たぶんミスだろう。」

そう判断して、コンパイルエラーにしてくれるわけです。

つまり、

変数に一度入れてから代入する
→ 構造的な互換性だけを見る(必要なものが揃っていればOK)

オブジェクトリテラルを直接代入する
→ 構造的な互換性 + 余剰プロパティチェック(余計なものがあればNG)

という二段構えになっています。


「足りない」側の部分一致は、そもそも通らない

必須プロパティが欠けているのは、常にアウト

ここまでの話は「余計なプロパティがある」ケースでした。
逆に、「必要なプロパティが足りない」ケースはどうでしょう。

type User = {
  name: string;
  age: number;
};

const p = {
  name: "Taro",
};

const u: User = p; // これは常にエラー
TypeScript

これは、変数を挟んでも挟まなくてもエラーです。

const u2: User = { name: "Taro" }; // もちろんエラー
TypeScript

TypeScript の基本ルールは、

必要なプロパティが全部揃っていることが最低条件
それに加えて、余計なプロパティがあってもいいかどうかは状況次第。」

なので、「足りない側の部分一致」は、どんな状況でも通りません。
ここはとてもシンプルです。


「部分一致が通らない理由」を言語化してみる

ケースごとに整理してみる

少し整理してみましょう。

「型 A に対して、型 B の値を代入したい」とき、TypeScript はこう見ています。

  1. A が要求するプロパティが、B に全部あるか
  2. そのプロパティの型が、A の期待と一致しているか
  3. もし右辺がオブジェクトリテラルなら、「A にないプロパティ」が書かれていないか

この3つのチェックのうち、

1 と 2 は「構造的な互換性」の話で、常に行われます。
3 は「余剰プロパティチェック」で、オブジェクトリテラルのときだけ追加で行われます。

だから、

「必要なプロパティが足りない」
→ 1 で落ちる(どんな状況でもNG)

「必要なプロパティは揃っていて、余計なプロパティもない」
→ 1, 2, 3 すべてOK(常に通る)

「必要なプロパティは揃っているが、余計なプロパティがある」
→ 右辺が変数なら 1, 2 だけ見るのでOK
→ 右辺がオブジェクトリテラルなら 3 で落ちる(部分一致が通らないように見える)

という挙動になります。


初心者がつまずきやすい「よくある勘違い」

「TypeScript は“名前付きの型”だから、完全一致じゃないとダメなんでしょ?」

これはよくある誤解です。
TypeScript は「名義的型システム」ではなく、「構造的型システム」です。

「User という名前の型だから User 同士しか代入できない」のではなく、
「User が要求する構造を満たしているかどうか」で判断します。

だからこそ、

type User = { name: string };
type Person = { name: string };

const u: User = { name: "Taro" };
const p: Person = u; // これはOK
TypeScript

というコードも普通に通ります。

「名前が同じかどうか」ではなく、「構造が合っているかどうか」。
そのうえで、「オブジェクトリテラルのときだけ余計なものに厳しい」というルールが乗っている——
この二層構造を意識できると、「部分一致が通らない理由」がかなりスッキリ見えてきます。


どう考えてコードを書けばいいか

「部分一致が通らない」と感じたとき、こう自分に問いかけてみてください。

その1:
「今自分が渡そうとしているオブジェクトは、その型が要求しているプロパティを全部持っているか?」

その2:
「今書いているのは“その場のオブジェクトリテラル”か?それとも、どこかで作られた変数か?」

その3:
「余計なプロパティがあるなら、それは本当に必要?それともスペルミスや設計ミス?」

この3つを順番に確認していくと、

「これは TypeScript が正しく止めてくれている」
「これは余剰プロパティチェックに引っかかっているだけだから、変数を挟むか、型を見直すべきだ」

といった判断ができるようになります。

部分一致が通らないのは、TypeScript が意地悪だからではなくて、
「足りないもの」「余計なもの」をちゃんと意識させるための仕組みです。

そこに気づけるようになると、エラーはただの邪魔者ではなく、
「設計のズレを教えてくれる相棒」に変わっていきます。

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