こんにちは!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は、あなたが書いたコードの「意図」を常に読み取ろうとしています。
ここまでの内容を理解できたなら、関数の型定義の基礎はもう完璧です。自信を持って、次のステップへ進んでくださいね。応援しています!