「部分一致が通らない」ってどういうことか
まず、ここでいう「部分一致」はだいたいこんな状況を指しています。
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; // これは通る?通らない?
TypeScriptTypeScript を触り始めると、
「型の一部だけ合っていれば代入できそうなのに、エラーになる」
「逆に、余計なプロパティがあっても代入できてしまう」
みたいな場面に出会って、「部分一致ってどう扱われてるの?」とモヤっとしがちです。
ここを整理するには、TypeScript が持っている二つの視点——「構造的な互換性」と「余剰プロパティチェック」を分けて見るのが近道です。
TypeScriptの基本ルール:「必要なものが全部あればOK」
構造的型システムという考え方
TypeScript は「構造的型システム」です。
ざっくり言うと、「その型が“どんなプロパティを持っているか”で互換性を判断する」というルールです。
type User = {
name: string;
};
const p = {
name: "Taro",
age: 20,
};
const u: User = p; // これは OK
TypeScriptここで TypeScript はこう考えています。
「User 型として必要なのは name: string だけ。p は name: 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" }; // もちろんエラー
TypeScriptTypeScript の基本ルールは、
「必要なプロパティが全部揃っていることが最低条件。
それに加えて、余計なプロパティがあってもいいかどうかは状況次第。」
なので、「足りない側の部分一致」は、どんな状況でも通りません。
ここはとてもシンプルです。
「部分一致が通らない理由」を言語化してみる
ケースごとに整理してみる
少し整理してみましょう。
「型 A に対して、型 B の値を代入したい」とき、TypeScript はこう見ています。
- A が要求するプロパティが、B に全部あるか
- そのプロパティの型が、A の期待と一致しているか
- もし右辺がオブジェクトリテラルなら、「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 が意地悪だからではなくて、
「足りないもの」「余計なもの」をちゃんと意識させるための仕組みです。
そこに気づけるようになると、エラーはただの邪魔者ではなく、
「設計のズレを教えてくれる相棒」に変わっていきます。
