【入門編】関数型における「デフォルト引数」と「オプショナル引数」の型推論の優先順位 – TypeScript コア・型システムの基礎解析バイブル

こんにちは!TypeScriptの世界へようこそ。

今日は、TypeScriptを学び始めたばかりの皆さんが必ず通る、そして意外と奥が深い「関数の引数」のお話をしましょう。

特に、「オプショナル引数(?)」と「デフォルト引数(=)」。どちらも「引数を省略できる」という点では似ていますが、TypeScriptのコンパイラが裏側でどう型を推論し、私たちのコードを守ってくれているのか、その仕組みを知ることで、あなたのコードの安全性は飛躍的に高まります。

「ここをクリアすれば、TypeScriptの基本はバッチリマスターできますよ」という温かい気持ちを込めて、じっくり解説していきますね。

—

1. 「?」で表現するオプショナル引数の世界

まずは、一番シンプルなオプショナル引数から見ていきましょう。

引数名の後ろに `?` をつけることで、「この値は渡しても渡さなくてもいいですよ」というサインになります。

// messageは必須、nameはオプショナル
function greet(message: string, name?: string) {
if (name) {
return `${message}, ${name}!`;
}
return message;
}

greet(“Hello”, “Alice”); // OK
greet(“Hi”); // OK: nameを省略できる

ここがポイント!

TypeScriptのコンパイラは、この `name?: string` を見た瞬間に、内部的に型を `string | undefined`(文字列または未定義)として扱います。

そのため、関数の中で `name` を使うときは、「もし `undefined` だったら?」というケースを考慮しないとエラーを出して教えてくれます。これがTypeScriptの優しさですね。

—

2. 「=」で表現するデフォルト引数の魔法

次に、デフォルト引数です。引数に直接 `=` で値を代入する書き方ですね。

function greetWithDefault(message: string, name = “Guest”) {
// nameは省略された場合、自動的に “Guest” になる
return `${message}, ${name}!`;
}

greetWithDefault(“Hello”, “Bob”); // “Hello, Bob!”
greetWithDefault(“Hi”); // “Hi, Guest!”

驚きの型推論

ここで面白いのが、型定義を明示的に書いていない(`: string` と書いていない)のに、TypeScriptは `name` を `string` 型だと完璧に理解している点です。

さらに、呼び出し側では `?` をつけていないのに、引数を省略することが許されます。デフォルト値がある=「値がなくても困らない」からですね。

—

3. 【本題】「?」と「=」が混在すると、推論はどう動く?

さて、ここからが「掌握」への第一歩です。もしこれらが混ざり合ったり、複雑な状況になったりした時、TypeScriptはどう判断するのでしょうか。

推論の優先順位と挙動

以下のコードを見てください。少し意地悪な例ですが、本質が詰まっています。

/

  • ケース:デフォルト値がある引数に、あえて「?」をつけたらどうなる?

/
function complexSetup(timeout = 1000, label?: string) {
// timeoutの型は number (undefinedが入り込む余地がない)
// labelの型は string | undefined
}

実は、「デフォルト引数」は「オプショナル引数」としての性質を内包しています。

1. 呼び出し側での視点: どちらも「省略可能」に見えます。
2. 関数内部での視点:

  • `?` の方は、値が `undefined` になる可能性があるため、常にチェックが必要です。
  • `=` の方は、たとえ呼び出し側で省略されても、実行時にデフォルト値が流し込まれるため、型から `undefined` が取り除かれます。

これが、TypeScriptが提供する「推論の優先順位」です。デフォルト値がある場合、型の安全性において「値が存在すること」が優先されるのです。

—

4. 陥りやすい罠:`undefined` を明示的に渡すとどうなる?

ここが、多くの開発者が最初に「おや?」と思うポイントです。

function test(value = “Default”) {
console.log(value);
}

test(undefined); // 実行結果: “Default”
test(null); // 実行結果: null (コンパイルエラーになる場合が多い)

なぜ `undefined` だけが特別なのか

TypeScript(およびJavaScript)の仕様では、「`undefined` を渡すこと」と「引数を省略すること」は、デフォルト値の文脈においてほぼ同義として扱われます。

  • `undefined` を渡す ➔ 「値がない」とみなされ、デフォルト値が使われる。
  • `null` を渡す ➔ 「『空』という値がある」とみなされ、デフォルト値は使われない(型定義で `null` を許容していない限り、TypeScriptがエラーで守ってくれます)。

—

5. 実践的な使い分けのガイドライン

プロの現場でどう使い分けるべきか、アーキテクトの視点で整理しましょう。

| 特徴 | オプショナル引数 (`?`) | デフォルト引数 (`=`) |
| :— | :— | :— |
| 主な用途 | 値がない状態に意味がある時(例:検索条件など) | 基本的な値が決まっている時(例:タイムアウト、リトライ回数) |
| 内部の型 | `T | undefined` | `T` (undefinedが消える!) |
| コードの簡潔さ | `if (arg)` などのチェックが必要 | チェック不要でそのまま使える |

迷ったら「デフォルト引数」を優先しよう

初心者の方へのアドバイスとして、「もし適切な初期値が決まっているなら、デフォルト引数を使う」のがおすすめです。

理由はシンプル。関数の中で `if (name !== undefined)` のようなチェックを書かなくて済むため、ロジックがスッキリし、バグが入り込む隙間がなくなるからです。

—

最後に:TypeScriptを掌握するあなたへ

いかがでしたか?
「?」と「=」、一見似ているこの二つには、「関数内部でその値がどう扱われるか」という決定的な違いがありました。

  • `?` は、呼び出し側に自由を与えるが、内部では慎重さを求める。
  • `=` は、呼び出し側に自由を与えつつ、内部には安心(非undefined)をもたらす。

この推論の挙動を理解しておけば、関数の設計で迷うことはもうありません。TypeScriptは、あなたが書いたコードの「意図」を常に読み取ろうとしています。

ここまでの内容を理解できたなら、関数の型定義の基礎はもう完璧です。自信を持って、次のステップへ進んでくださいね。応援しています!

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