「オブジェクトリテラルの型推論」とは何をしてくれているのか
オブジェクトリテラルとは、{ ... } で直接書いたオブジェクトのことです。
const user = {
name: "Taro",
age: 20,
};
TypeScriptここで型注釈(: { name: string; age: number } など)を書いていなくても、
TypeScript はこのオブジェクトから「プロパティ名」と「値の型」を読み取って、
自動的に型を作ってくれます。これが「オブジェクトリテラルの型推論」です。
つまり、上の user はこう推論されます。
// 推論された型(イメージ)
{
name: string;
age: number;
}
TypeScript「書いたものから型を起こしてくれる」——ここをちゃんと理解しておくと、
どこまで型注釈を書くべきか、どこは推論に任せていいかの感覚がかなりクリアになります。
基本:プロパティの「名前」と「値の型」から型が作られる
シンプルな例で型推論の動きを見る
const product = {
id: 1,
name: "Book",
price: 1200,
inStock: true,
};
TypeScriptこのとき TypeScript は、次のように解釈します。
idの値は1→ 型はnumbernameの値は"Book"→ 型はstringpriceの値は1200→ 型はnumberinStockの値はtrue→ 型はboolean
なので、product の型はこう推論されます。
{
id: number;
name: string;
price: number;
inStock: boolean;
}
TypeScriptここで重要なのは、「リテラル値そのもの(1 や “Book”)ではなく、その“種類”が型になる」ということです。
何も特別なことをしなければ、"Book" は "Book" というリテラル型ではなく、string として扱われます。
代入先の型があるときの推論(「合わせにいく」動き)
代入先の型に合わせて解釈される
type User = {
name: string;
age: number;
};
const u: User = {
name: "Taro",
age: 20,
};
TypeScriptこの場合、TypeScript はまず User の型を見ます。
nameは stringageは number
そのうえで、右側のオブジェクトリテラルがこの形に合っているかをチェックします。
ここでのポイントは、「右側のオブジェクトの“推論された型”が User に代入可能か?」という観点で見ていることです。
もし間違った値を入れると、すぐに怒られます。
const u2: User = {
name: "Taro",
age: "20", // Type 'string' is not assignable to type 'number'.
};
TypeScript「オブジェクトリテラルの型推論」は、
「右側から型を作る」だけでなく、「左側の型と照らし合わせてチェックする」ところまで含めて考えると理解しやすいです。
追加プロパティチェック(余計なプロパティは弾かれる)
オブジェクトリテラルにだけ厳しく働くチェック
type User = {
name: string;
age: number;
};
const u: User = {
name: "Taro",
age: 20,
isAdmin: true,
// Object literal may only specify known properties...
};
TypeScriptここで TypeScript は、「User にないプロパティ isAdmin が書かれている」とエラーにします。
これを「追加プロパティチェック」と呼びます。
重要なのは、「オブジェクトリテラルを直接代入するときだけ、特に厳しくチェックされる」という点です。
一度変数を挟むと、少し緩くなります。
const raw = {
name: "Taro",
age: 20,
isAdmin: true,
};
const u2: User = raw; // OK(追加プロパティチェックは働かない)
TypeScriptここでは「raw の型が User に代入可能か?」というチェックだけになり、
余計なプロパティがあってもエラーにはなりません。
オブジェクトリテラルの型推論は、
「その場で書いたオブジェクトに対しては、間違いを早めに見つけるために厳しく見る」
という性格を持っています。
as const とオブジェクトリテラルの型推論
通常は「ざっくり型」、as const は「カチカチの型」
const config = {
env: "dev",
debug: true,
};
TypeScriptこのときの型は、
{
env: string;
debug: boolean;
}
TypeScriptです。"dev" は string として扱われます。
一方、as const を付けるとこうなります。
const config = {
env: "dev",
debug: true,
} as const;
TypeScript推論される型は、
{
readonly env: "dev";
readonly debug: true;
}
TypeScriptになります。
ここで起きていることは、
- プロパティが
readonlyになる(書き換え禁止) - 値がリテラル型になる(
"dev"という“文字列の一種”ではなく、「”dev” という値そのもの」)
という2つです。
「このオブジェクトは設定値で、あとから変えないし、値のバリエーションも決まっている」
というときには、as const を付けることで、
「この形と値をそのまま型にする」ことができます。
オブジェクトリテラルの型推論と「部分的な型注釈」
一部だけ型を決めて、残りは推論に任せる
const user: { name: string; age: number } = {
name: "Taro",
age: 20,
};
TypeScriptこれは全部を明示していますが、
ときには「変数の型だけ決めて、中身は推論に任せる」という書き方もよく使います。
type User = {
name: string;
age: number;
};
const user: User = {
name: "Taro",
age: 20,
};
TypeScriptここでは、右側のオブジェクトリテラルは「User に合っているか?」だけ見られます。
プロパティごとに型を書かなくても、User がそれを表してくれています。
逆に、右側だけに型注釈を書くこともできます。
const user = {
name: "Taro" as string,
age: 20 as number,
};
TypeScriptただし、これはあまりやりません。
「オブジェクト全体の型」をどこかに定義しておいて、それを使い回す方が、
設計としても読みやすさとしても良いです。
初心者がまず掴んでおきたい「オブジェクトリテラルの型推論」の感覚
大事なポイントをまとめると、こんな感じです。
オブジェクトリテラルを書いた瞬間に、TypeScript は
- プロパティ名をそのまま型の「キー」として使い
- 値の「種類」からプロパティの型を決め
- 代入先の型があれば、それと照らし合わせてチェックし
- その場で書いたオブジェクトには、余計なプロパティがないかも厳しく見る
ということを自動でやっています。
だからこそ、
「まずは型注釈なしで書いてみて、推論された型をエディタで確認する」
「よく使う形は type / interface に切り出して、代入先の型として使う」
「設定値など“変えないもの”は as const でカチッと固める」
こういう使い方をしていくと、
オブジェクトリテラルの型推論は「勝手にやってくれる便利機能」から、
「設計の意図をそのまま型にしてくれる相棒」に変わっていきます。
