TypeScriptを書いていると、型を指定していないのに「あれ、ちゃんと型が付いてる?」と感じる場面がありますよね。

型を書いていないのに、なんでエラーが出るの?
どこまで型を省略してよくて、どこから書かないといけないの?
全部の変数・引数・関数に型を書かないといけないとしたら、大変すぎる…

その正体が「型推論」です。TypeScriptには、コードを見て型を自動的に判断してくれる仕組みが備わっています。この記事では、型推論が何をしてくれるのか・どういう場面で効いているのか・どんな場面では自分で型を書く必要があるのか、を丁寧に解説します。

この記事は次のような方におすすめです。

この記事はこんな人におすすめ!
  • TypeScriptを書き始めたばかりで、型の書き方に慣れていない
  • 型を書かなくても動くコードを見て「なぜ?」と疑問に思った
  • どこまで型を省略してよいのかわからず、全部に型を書いている
  • 型推論という言葉を聞いたことはあるが、仕組みをちゃんと理解していない

この記事を読むと、「TypeScriptが型を決めてくれる場面」と「自分で型を書かなければいけない場面」がはっきり区別できるようになります。型推論を正しく理解すると、無駄に型を書きすぎることもなくなり、コードがスッキリします。

それでは、順を追って詳しく見ていきましょう!

型推論とは — TypeScriptが型を「自動で読む」仕組み

型推論とは — TypeScriptが型を「自動で読む」仕組み

型推論(type inference)とは、TypeScriptがコードの文脈から型を自動的に判別する仕組みのことです。 型注釈(: string: number のように変数の後ろに書く型の指定)を書かなくても、TypeScriptは「この変数は string 型だな」と判断して、型チェックを行ってくれます。

「型チェックをしてくれる」というTypeScriptの最大の特徴は、実は型推論があってこそ成り立っています。全部の変数に自分で型を書かなくても、TypeScriptは型安全を守り続けてくれるのです。

型推論が動く最小の例

次のコードを見てください。型注釈を一切書いていませんが、VSCodeなどのエディターで変数の上にカーソルを乗せると、TypeScriptが自動的に型を決めていることが確認できます。

const message = "こんにちは";
// TypeScriptが "message は string 型" と判断する

const count = 42;
// TypeScriptが "count は number 型" と判断する

const isReady = true;
// TypeScriptが "isReady は boolean 型" と判断する

message": string" と書いていないのに、TypeScriptは「初期値が文字列だから string 型だ」と判断してくれます。その結果、こんな操作はちゃんとエラーになります。

const message = "こんにちは";
message.toFixed(2); // エラー!string に toFixed はない

型を書かなくても、型安全は担保されています。 これが型推論のポイントです。

型注釈を書いた場合との比較

型注釈を書いた場合と、型推論に任せた場合を並べると、次のようになります。

// 型注釈あり(自分で型を指定する)
let name: string = "TypeScript";

// 型推論に任せる(型注釈を省略する)
let name = "TypeScript";

どちらも namestring 型になります。初期値から型が明らかな場合は、型注釈を省略しても問題ありません。 むしろ、「初期値を見れば型がわかる変数に型注釈を書くのはくどい」という考え方が一般的です。

変数に対する型推論 — constとletで結果が変わる

変数に対する型推論 — constとletで結果が変わる

TypeScriptの型推論は、変数に使うキーワード(constlet か)によって、推論される型が変わります。 これは最初に知っておくと、あとで混乱しにくくなるポイントです。

constの場合:値がそのままリテラル型になる

const で宣言した変数は、値を後から変えられないことが保証されています。そのため、TypeScriptは「この変数は、この値のまま変わらない」と判断して、値そのものを型として推論します。

const status = "active";
// 推論される型: "active"(string ではなく、文字列リテラル型)

status の型は string ではなく "active" です。 "active" という値しかありえない、と TypeScript が判断するわけです。これを「リテラル型」と呼びます。

letの場合:より広い型に推論される

let で宣言した変数は、後から値を変えられます。そのため、TypeScriptは「どんな文字列に変わるかわからない」と判断して、string という広い型で推論します。

let status = "active";
// 推論される型: string("active" ではなく、string 型全体)

status = "inactive"; // これは OK(どちらも string 型)

constlet で同じ値を入れても、推論される型が違います。 これを覚えておくと、「なんでこの型になるんだろう?」と迷う場面が減ります。

なぜconstとletで違う挙動になるのか

理由は「値が変わる可能性があるかどうか」です。

  • const → 値は変わらない → 今の値そのものを型にしてよい → リテラル型
  • let → 値が変わりうる → 広い型にしておく必要がある → string / number など

TypeScriptがこう動くのは、型安全のためです。let で宣言した変数を「"active" しか入らない」と型推論してしまうと、後で別の文字列を代入したときにエラーが出てしまいます。それを避けるために、let の場合は広い型で推論されます。

関数に対する型推論 — 戻り値は推論される、引数はされない

関数に対する型推論 — 戻り値は推論される、引数はされない

関数に対する型推論は、「どこが推論されて、どこはされないか」がやや非対称なので、ここをしっかり理解しておくと混乱しにくくなります。

関数の戻り値:型は書かなくても推論される

関数が何を返すかは、return の内容からTypeScriptが判断してくれます。

function double(n: number) {
  return n * 2;
}
// double の戻り値は推論で number 型になる

return n * 2 の結果が number 型であることをTypeScriptが読み取り、この関数の戻り値型を number と推論します。: number を戻り値の位置に書かなくても、型チェックは正しく機能します。

const result = double(5);
result.toUpperCase(); // エラー!number に toUpperCase はない

戻り値の型を書かなくても、こういったエラーはちゃんと検出されます。

関数の引数:単体で書く場合は型の明示が必要

一方で、次のような通常の関数宣言・関数式では、引数の型は推論されません。型を明示しないと、strict モードが有効な環境では次のようなエラーが出ます。

// strict モードが有効な場合はエラーになる
function double(n) {
  return n * 2;
}
// エラー: Parameter 'n' implicitly has an 'any' type.

引数に渡される値の型は呼び出し元が決めるものであり、関数の定義だけでは確定しません。 そのため、こういった場合は引数の型を明示してください。

// 引数に型を明示する
function double(n: number) {
  return n * 2;
}

なお、array.map()array.filter() のようなコールバック関数として渡す場合は、配列の要素型から引数が自動的に推論されることがあります(「Contextual Typing(文脈的型付け)」と呼ばれる仕組みです)。

const numbers = [1, 2, 3];
numbers.map((n) => n * 2);
// n は配列の要素型から number と推論される

型を書くべき場面・省略してよい場面

型を書くべき場面・省略してよい場面

「型推論があるなら全部省略していいの?」という疑問も当然出てきます。型を省略してよい場面と、ちゃんと書いた方がよい場面を整理しておきます。

省略してよいケース

次のような場面は、型推論に任せてOKです。

変数に初期値を代入するとき

let name = "TypeScript";    // string と推論される
let version = 5;            // number と推論される
let isActive = true;        // boolean と推論される

初期値を見れば型が一目でわかる場合は、型注釈を書かない方がコードがスッキリします。

関数の戻り値

function add(a: number, b: number) {
  return a + b; // 戻り値の型 number は推論に任せてよい
}

シンプルな関数であれば、戻り値の型注釈は省略するのが一般的です。

明示的に書いた方がよいケース

一方で、次のような場面は明示的に型を書くことをおすすめします。

関数の引数(必須)

前述の通り、引数は型推論がかからないので必ず書きます。

function greet(name: string) {
  return `こんにちは、${name}さん`;
}

変数に初期値がないとき

// 初期値がなければ、TypeScriptは型を決められない
let value; // any 型になってしまう

// こういう場合は型を書く
let value: string;
value = "あとから代入する";

複雑な戻り値を返す関数

処理が複雑で、戻り値の型を明示しておかないと読み手が把握しにくい場合は、ドキュメントとして型を書くのも有効です。チームで開発しているときは特に有用です。

// 複雑な処理では戻り値の型を明示しておくと親切
function fetchUser(id: number): { name: string; age: number } {
  // ...
}

外部に公開するAPIの境界

複数のファイルにまたがって使う関数や、他の人も呼び出す関数では、引数だけでなく戻り値の型も明示しておくのがおすすめです。

理由は2つあります。

  1. 「この関数は何を受け取って何を返すのか」が、コードを読むだけでわかるようになる
  2. 関数の内部を変更したときに、戻り値の型が意図せず変わってしまうミスを防げる
// 戻り値の型を明示しておくと、関数の「約束」が明確になる
function getUser(id: number): { name: string; age: number } {
  // この関数が { name: string; age: number } を返すことを
  // TypeScriptが常にチェックしてくれる
  return { name: "Taro", age: 25 };
}

型推論に任せていると、内部の実装を変えたときに戻り値の型がこっそり変わっても気づきにくいことがあります。外部から呼ばれる関数では、戻り値の型を明示することで「この関数はこの型を返す」という約束をTypeScriptに守らせることができます。

なかむぅ
なかむぅ
なお、この考え方は複数人での開発や、ファイルをまたぐ処理が増えてきたときにより実感しやすくなります。最初のうちは「複雑な関数には戻り値の型も書く」くらいの感覚で覚えておけば十分です。

【付録】さらに学びを深めるためのリソース


さらにTypescriptの学習を進めたい方のために、いくつかのリソースを紹介します。
これらのリソースを活用することで、TypeScriptの型システムについてより深い知識を得ることができるでしょう。

おすすめの書籍

ゼロからわかる TypeScript入門


技術評論社から出版されている「ゼロからわかる TypeScript入門」は、プログラミング初心者や本職プログラマーではない方を主な対象にした入門書です。

変数・条件分岐・ループといった基本から、クラスやインターフェース、モジュールまで段階的に学べる構成になっています。最終章ではWeb APIとJSONを使った非同期Webアプリの作成も体験できるので、「実際に動くものを作る」ところまで到達できます。

プロを目指す人のためのTypeScript入門


技術評論社の「プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで」、通称 ブルーベリー本 です。
JavaScriptの仕様とTypeScript独自の機能を両方押さえつつ、リテラル型・ユニオン型・keyof型・ジェネリクスなど、高度な型表現まで踏み込んで解説しています。TypeScriptの型システムの表現力を本格的に学べる一冊です。

オンラインで参照できる公式ドキュメント

TypeScript公式ハンドブック


https://www.typescriptlang.org/docs/
TypeScriptの公式ドキュメントです。
intersection型を含む、すべての型システムの機能について詳細な説明があります。

TypeScript Deep Dive


https://basarat.gitbook.io/typescript/
TypeScriptの深い部分まで掘り下げて解説しているオンラインブックです。
無料で読むことができ、intersection型についても詳しく説明されています。

TypeScriptの学習は終わりがありません。
新しい機能が常に追加され、より良い書き方が発見されています。
継続的に学習を続けることで、より良いTypeScriptプログラマーになれるはずです。


まとめ — 型推論のルールを整理する

まとめ — 型推論のルールを整理する

この記事で扱った型推論のポイントをまとめます。

場面 型推論の有無 おすすめの対応
変数(初期値あり) 推論される 省略してOK
変数(初期値なし) 推論されない 型を明示する
const の変数 リテラル型で推論される 省略してOK
let の変数 広い型(string等)で推論される 省略してOK
関数の引数 推論されない 必ず明示する
関数の戻り値 推論される 省略してOK(複雑な場合は明示)

型推論のおかげで、「TypeScriptは全部に型を書かなければいけない」というわけではありません。書かなくていい場所では省略し、書くべき場所ではきちんと書く——このバランスが取れると、TypeScriptのコードがぐっと書きやすくなります。

※本記事の本文案はAIを活用して作成していますが、記載している内容およびコードは筆者が実際に調査、検証・実行し、内容の正確性を確認した上で公開しています。