TypeScript型推論とWidening:初心者向け完全ガイド
はじめに
TypeScriptを使い始めると、「型を毎回手で書くのは面倒だな」と感じることがあるかもしれません。実はTypeScriptには、値や文脈から自動的に型を判定してくれる「型推論」(Type Inference)という仕組みが備わっています。型推論を理解することで、必要最小限の型アノテーションで型安全なコードを書けるようになります。
本記事では、TypeScriptの型推論がどのように機能するかを、初心者の方にもわかりやすくご説明します。letとconstによる推論の違いから、Widening(型の拡大)や文脈型付けまで、コード例を交えて丁寧に解説していきます。
型推論の基本的な仕組みと型アノテーションの省略
TypeScriptでは、変数を宣言するときに型アノテーション(型注釈)を使って型を指定できます。型アノテーションとは、変数名の後ろにコロンと型名を書く記法のことです。
const name: string = "Michael Jackson";
このコードでは、変数nameにstringという型アノテーションを付けています。しかし、右辺の値を見れば"Michael Jackson"は明らかに文字列です。このような場合、TypeScriptは型アノテーションがなくても値から型を自動的に推測できます。
const name = "Michael Jackson";
このように、TypeScriptが値や文脈から自動的に型を決定する仕組みを「型推論」と呼びます。型推論はTypeScriptの型システムの中核をなす機能です。開発者は型アノテーションを省略しつつも、型安全なコードを書くことができます。
以下の図は、TypeScriptが型推論を行う流れを示しています。
flowchart TD
A[変数宣言] --> B{型アノテーションあり?}
B -->|あり| C[指定された型を使用]
B -->|なし| D[右辺の値を分析]
D --> E{値の種類を判定}
E -->|文字列| F[string型として推論]
E -->|数値| G[number型として推論]
E -->|真偽値| H[boolean型として推論]
E -->|オブジェクト| I[プロパティ構造から推論]
この図のように、型アノテーションが省略された場合、TypeScriptは右辺の値を分析して適切な型を自動的に決定します。
型推論によって得られるメリット
型推論を活用することで、コードの記述量を減らしながらも型の安全性を維持できます。たとえば、IDEやエディタ上でホバーすると、TypeScriptが推論した型情報がツールチップに表示されます。型アノテーションを省略しても、型の情報をいつでも確認できるのです。
ただし、型推論に頼りすぎると意図しない型が推論されるケースもあります。関数のパラメータなど、文脈だけでは型を特定できない場面では、明示的に型アノテーションを書くことが推奨されます。
なお、JavaScriptにもtypeof演算子という型を調べる仕組みがありますが、これは実行時に動的に型を判定するものです。TypeScriptの型推論はコンパイル時(静的解析の段階)に行われるため、コードを実行する前に型が決定されるという根本的な違いがあります。
型アノテーションやプリミティブ型の基本については、以下の記事で詳しく解説しています。
letとconstで変わるプリミティブ型の推論結果
TypeScriptの型推論では、変数をletで宣言するかconstで宣言するかによって、推論される型が大きく変わります。この違いを理解することは、TypeScriptの型推論を正しく使いこなすうえで非常に重要です。
let宣言で推論されるプリミティブ型
letで宣言した変数に文字列リテラルを代入した場合、TypeScriptはこの変数の型をstring型と推論します。
let name = "Michael Jackson";
// 推論される型: string
letで宣言した変数は再代入が可能です。そのため、TypeScriptは特定の文字列リテラルではなく、より一般的なstring型として推論します。数値リテラルの場合はnumber型、真偽値リテラルの場合はboolean型として推論されます。
let age = 50; // 推論される型: number
let isActive = true; // 推論される型: boolean
const宣言で推論されるリテラル型
一方、constで宣言した変数の場合は挙動が異なります。
const name = "Michael Jackson";
// 推論される型: "Michael Jackson"(リテラル型)
constで宣言した変数は再代入ができません。TypeScriptはこの値が変わることはないと判断し、より狭い型である「リテラル型」として推論します。上の例では、nameの型はstringではなく"Michael Jackson"という文字列リテラル型になります。
リテラル型とは、特定の値そのものを型として扱う仕組みです。"Michael Jackson"型の変数には、"Michael Jackson"という値しか代入できません。
以下の図は、letとconstで推論される型の違いを示しています。
flowchart LR
A["値: Michael Jackson"] --> B{宣言キーワード}
B -->|let| C[string型に推論]
B -->|const| D[リテラル型に推論]
C --> E[再代入可能なため広い型]
D --> F[再代入不可のため狭い型]
このように、letとconstでは推論される型の幅が異なります。TypeScriptの型推論は、変数の再代入可能性を考慮して型の範囲を決定しています。
オブジェクト型における型推論と構造的型付け
TypeScriptの型推論はプリミティブ型だけでなく、オブジェクトや関数にも適用されます。ここでは、オブジェクト型と関数型における型推論の仕組みをご説明します。
オブジェクトリテラルのプロパティ型推論
TypeScriptは、変数にオブジェクトリテラルを代入すると、各プロパティの型を自動的に推論します。
const person = { name: "Michael Jackson", age: 50 };
// 推論される型: { name: string; age: number }
この場合、personは{ name: string; age: number }と推論されます。各プロパティの型は、代入された値から自動的に決定されます。
TypeScriptは名前ベースではなく構造ベースで型を比較します。この仕組みを「構造的型付け」(Structural Typing)と呼びます。「そのオブジェクトがどんなプロパティを持っているか」に着目して型の互換性を判定する考え方です。
IDEでホバーすると、推論された型が{ name: string; age: number }のように表示されます。これは、nameとageという2つのプロパティを持つオブジェクト型であることを示しています。
関数の引数と戻り値の型推論
関数の場合、TypeScriptは戻り値の型を関数本体のreturn文から推論します。
const getFullName = (person: { name: string; age: number }) => {
return person.name;
};
// 戻り値の型: string と推論される
この例では、person.nameがstring型であるため、関数の戻り値もstring型と推論されます。
ただし、関数のパラメータの型は自動的に推論できないケースが多いため、明示的に型アノテーションを書くことが一般的です。パラメータの型を省略すると、TypeScriptは暗黙的にany型として扱う場合があります。
Wideningの仕組みと型が拡大される条件
Widening(ワイドニング)とは、TypeScriptが型推論の際に、狭い型(リテラル型)をより広い型(プリミティブ型)に自動的に拡大する仕組みです。この概念を理解することで、意図しない型推論の結果を回避できます。
プリミティブ型のliteral type widening
先ほど、constで宣言した変数はリテラル型として推論されることをご説明しました。このリテラル型の値をletで宣言された変数に代入すると、型が自動的に拡大されます。
const name = "Michael Jackson";
// const name: "Michael Jackson"(リテラル型)
let broadName = name;
// let broadName: string(string型に拡大)
constで宣言したnameは"Michael Jackson"型ですが、それをletで宣言されたbroadNameに代入すると、型はstringに拡大されます。
letで宣言された変数は再代入が可能なため、特定のリテラル型に限定するのではなく、同じ分類の型全体を受け入れられるようにする必要があります。この仕組みを「literal type widening」と呼びます。
オブジェクトのプロパティで発生するWidening
Wideningはオブジェクトのプロパティでも発生します。プリミティブ型の場合、constで宣言するとリテラル型が推論されました。しかし、オブジェクトの場合はconstで宣言してもプロパティの型が拡大されます。
const name = "Michael Jackson";
// 型: "Michael Jackson"(リテラル型が保持される)
const user = { name: "Michael Jackson" };
// 型: { name: string }(stringに拡大される)
constはオブジェクトの再代入を防ぎますが、プロパティの変更は防げません。以下のコードを見てみましょう。
const user = { name: "Michael Jackson" };
user.name = "Janet Jackson"; // プロパティの変更は可能
もしnameを"Michael Jackson"リテラル型として推論したら、user.name = "Janet Jackson"がコンパイルエラーになってしまいます。しかし、実際のJavaScriptではこの代入は問題なく動作します。
TypeScriptはJavaScriptの動作を壊さないことを重視しています。そのため、オブジェクトのプロパティは自動的にWideningされるのです。これはプリミティブ値が不変(イミュータブル)であるのに対して、オブジェクトが可変(ミュータブル)であるという性質に由来しています。
以下の図は、constの宣言対象によるWideningの有無を示しています。
flowchart TD
A[const宣言] --> B{値の種類}
B -->|プリミティブ値| C[リテラル型を保持]
B -->|オブジェクト| D[プロパティの型を判定]
D --> E{プロパティは変更可能?}
E -->|はい| F[Wideningが発生]
F --> G[プリミティブ型に拡大]
C --> H[再代入不可で型は狭いまま]
この図のように、constで宣言してもオブジェクトのプロパティではWideningが発生します。リテラル型を保持したい場合は、as const(const assertion)を使う方法がありますが、これについては別の記事で詳しくご紹介します。
as constやreadonlyの使い方については、以下の記事で解説しています。
文脈型付けによる暗黙的な型推論の仕組み
TypeScriptには、コードの文脈からどのような型が期待されるかを判断し、自動的に型を決定する「文脈型付け」(Contextual Typing)という仕組みがあります。文脈型付けは、特にコールバック関数を扱う場面でよく登場します。
配列メソッドにおけるコールバックの型推論
文脈型付けが最もよく使われるのは、配列メソッドのコールバック関数です。以下の例を見てみましょう。
const users: Array<{ name: string; age: number }> = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
];
const names = users.map((user) => user.name);
// names の型: string[]
// user は自動的に { name: string; age: number } と推論される
この例では、usersが{ name: string; age: number }の配列であることをTypeScriptが把握しています。そのため、.map()のコールバックに渡されるuserパラメータの型を自動的に推論できます。
コールバック関数のパラメータに型アノテーションを書く必要はありません。配列の要素型から自動的にパラメータの型が決定されるため、コードを簡潔に保てます。
const ages = users.map((user) => user.age);
// ages の型: number[]
const descriptions = users.map(
(user) => `${user.name} is ${user.age} years old`
);
// descriptions の型: string[]
以下の図は、文脈型付けによる型推論の流れを示しています。
flowchart TD
A[配列usersの型を確認] --> B[要素型を特定]
B --> C[.mapメソッドを呼び出し]
C --> D[コールバックのパラメータ型を推論]
D --> E["user.name → string型"]
D --> F["user.age → number型"]
E --> G["戻り値: string配列"]
F --> H["戻り値: number配列"]
この図のように、配列の型情報がコールバック関数のパラメータ型に伝播していきます。これが文脈型付けの基本的な仕組みです。
変数宣言時の型注釈と文脈型付け
文脈型付けは、コールバック関数だけでなく変数宣言の場面でも発生します。型注釈を持つ変数に値を代入する際、右辺の式は左辺の型に基づいて型チェックが行われます。
const greeting: string = "Hello, World!";
// 右辺は左辺の string 型に基づいてチェックされる
また、関数の戻り値型が明示されている場合、return文の式もその型に基づいてチェックされます。文脈型付けは、TypeScriptのさまざまな場面で暗黙的に働いている仕組みです。
文脈型付けの対象は広範に及びます。satisfies演算子やNoInferユーティリティ型など、より高度な文脈型付けの活用方法については、次の記事で詳しく取り扱います。
終わりに
本記事では、TypeScriptの型推論の仕組みについて、基本的な概念からWidening、文脈型付けまでを解説しました。型推論は開発者の負担を軽減しながら型安全性を保つ、TypeScriptの中核的な機能です。
特に、letとconstで推論結果が異なる点や、オブジェクトのプロパティでWideningが発生する点は、実務でも頻繁に遭遇する挙動です。これらの仕組みを理解しておくことで、意図しない型エラーを回避しやすくなります。
本記事の内容はTypeScript 5.x系(2026年2月時点)で検証しています。型推論の理解を深めたら、次のステップとしてas constやsatisfies演算子についても学んでみてください。
オブジェクト型に名前を付けて管理するinterfaceについては、以下の記事で解説しています。
TypeScript公式ドキュメント – Type Inference
コメントを残す