こんにちは!開発の進捗はいかがですか?
TypeScriptの世界へようこそ。型システムと聞くと最初は「覚えることが多くて大変そう…」と感じるかもしれませんが、仕組みの本質(コンパイラがどう思考し、実行時にJavaScriptがどう動くか)を一度理解してしまえば、これほど強力で頼もしい相棒はありません。
今回は、関数の型定義において誰もが一度は混乱する「オプショナル引数(`?`)」と「デフォルト値付き引数(`= デフォルト値`)」の微妙な差異について、深く、そして分かりやすく解説します。
一見すると「どちらも引数を省略できるようにするもの」に見えますよね。しかし、「関数内部で扱う型」と「実行時の動作」において、TypeScriptコンパイラは明確にこれらを区別しています。
ここをスッキリクリアできれば、関数定義の基本はバッチリマスターできますよ!一緒に紐解いていきましょう。
—
1. オプショナル引数(`?`)の本質:「あってもなくても良い(無ければ undefined)」
まずは、引数名の後ろに `?` をつける「オプショナル引数」から見ていきましょう。
基本的な書き方と型推論
// 引数 name の後ろに ? を付与
function printMessageOptional(name?: string): void {
// ————————————————————-
// [関数内部での型評価]
// コンパイラ視点:name の型は “string | undefined” と推論される!
// ————————————————————-
// そのため、そのまま string のメソッドを呼ぶとエラーになります
// console.log(name.toUpperCase()); // ❌ エラー: ‘name’ は ‘undefined’ の可能性があります。
// 対策:安全に扱うために「型ガード(Narrowing)」や「オプショナルチェーン」が必要
if (name !== undefined) {
// このブロック内では name は “string” 型に絞り込まれる
console.log(`こんにちは、${name.toUpperCase()}さん!`);
} else {
console.log(“こんにちは、ゲストさん!”);
}
}
// — 呼び出し側の挙動 —
printMessageOptional(“Alice”); // OK: string を渡す
printMessageOptional(); // OK: 引数を省略(内部では undefined になる)
printMessageOptional(undefined); // OK: 明示的に undefined を渡す
コンパイラ内部で起きていること
`name?: string` と書いた瞬間、TypeScriptは呼び出し側に対して「省略可能」フラグを立てると同時に、関数内部における `name` の型を `string | undefined`(Union型) に自動的に拡張します。
つまり、`?` を使うということは、「関数内部で `undefined` が回ってくるリスクを自前でハンドリングします」とコンパイラに宣言していることになります。
—
2. デフォルト値付き引数(`=`)の本質:「無ければ補完する(内部に undefined を持ち込まない)」
次に、引数に `= “初期値”` を設定する「デフォルト値付き引数」を見てみましょう。
基本的な書き方と型推論
// 引数 name にデフォルト値を設定
function printMessageDefault(name: string = “ゲスト”): void {
// ————————————————————-
// [関数内部での型評価]
// コンパイラ視点:name の型は “string” と推論される!(undefined は混ざらない)
// ————————————————————-
// 💡 安全に string のメソッドをそのまま呼び出せる!
console.log(`こんにちは、${name.toUpperCase()}さん!`);
}
// — 呼び出し側の挙動 —
printMessageDefault(“Alice”); // 出力: こんにちは、ALICEさん!
printMessageDefault(); // 出力: こんにちは、ゲストさん! (デフォルト値が採用される)
printMessageDefault(undefined); // 出力: こんにちは、ゲストさん! (undefinedを渡すとデフォルト値が発動)
コンパイラとJavaScript実行エンジンの連携
ここが最も面白いポイントです!
`name: string = “ゲスト”` と書いた場合:
1. 呼び出し側(関数の外)から見れば:引数を省略することも、`undefined` を渡すことも許可されます。
2. 関数内部から見れば:型は `undefined` が除外された `string` 単一 になります。
なぜ内部で `undefined` が排除できるのでしょうか?
それは、JavaScriptの実行時(ランタイム)仕様として「引数が省略されるか `undefined` が渡された場合、自動的にデフォルト値を代入する」という保証があるからです。
TypeScriptコンパイラはJavaScriptのこの挙動を完璧に理解しているため、「関数内部に入った時点で `undefined` である可能性はゼロだ」と判断し、型を `string` に絞り込んでくれるのです。非常に賢いですよね!
—
3. 図解で一目瞭然!型と実行時のメンタルモデル
2つの違いを視覚的に整理してみましょう。関数の「外側(呼び出し側)」と「内側(処理系)」で、型の見え方がどう変化しているかに注目してください。
【オプショナル引数: name?: string】
[ 呼び出し側 ] [ 関数内部 ]
“Alice” ───( string )────────────────> 型: string | undefined
(省略) ───( undefined )─────────────> 値: “Alice” または undefined
undefined ───( undefined )─────────────> ※ undefined チェックが必要!
【デフォルト値: name: string = “ゲスト”】
[ 呼び出し側 ] [ 関数内部 ]
“Alice” ───( string )────────────────> 型: string (確定!)
(省略) ───( 評価: undefined ) ─┐ 値: “Alice” または “ゲスト”
undefined ───( 評価: undefined ) ─┴─(JSが”ゲスト”に変換)─> ※ undefined チェック不要!
比較まとめ表
| 比較項目 | オプショナル引数 (`name?: string`) | デフォルト値付き引数 (`name: string = “ゲスト”`) |
| :— | :— | :— |
| 呼び出し側の型 | `string \| undefined`(省略可) | `string \| undefined`(省略可) |
| 関数内部での型 | `string \| undefined` | `string` |
| 内部での `undefined` 対策 | 必要(If文やオプショナルチェーン) | 不要(JSが自動補完するため) |
| 型推論の書き方 | `name?: string` | `name = “ゲスト”`(型注釈 `: string` も省略可) |
—
4. 現場で誰もがハマる!2つの罠(注意点)
ここで、実務でエンジニアがよく遭遇する「陥りやすい罠」を2つ紹介します。ここを押さえておけば、チームのコードレビューでも一目置かれる存在になれますよ。
罠1:`null` を渡したときの挙動の違い
JavaScript/TypeScriptにおいて、`undefined` と `null` は明確に区別されます。
デフォルト値が発動するのは `undefined` のみ です。
function greet(name: string = “ゲスト”) {
console.log(`Hi, ${name}`);
}
// ❌ コンパイルエラーになります!
// エラー: 型 ‘null’ の引数を型 ‘string | undefined’ のパラメータに割り当てることはできません。
greet(null);
もし仮に `any` や型キャスト等で `null` が渡されてしまった場合、JavaScriptの仕様上デフォルト値は適用されず、`null` がそのまま通過して実行時エラーの原因になります。
「デフォルト値=`undefined` のときのみ発動する」というルールは、しっかり脳内にインプットしておきましょう。
罠2:引数の順番(デフォルト値/オプショナルは末尾に置く)
どちらの構文を使う場合も、原則として「省略可能な引数は、必須の引数よりも後ろ(右側)」に配置する必要があります。
// ❌ 悪い例:省略可能な引数が先頭にある
function createCardBad(isVip: boolean = false, userId: string) {
// …
}
// 呼び出すときに省略しようとしても、第1引数に位置するため省略できない!
// createCardBad(“user-123”); // ❌ エラー!
// ⭕️ 良い例:省略可能な引数は末尾に配置する
function createCardGood(userId: string, isVip: boolean = false) {
console.log(`User: ${userId}, VIP: ${isVip}`);
}
createCardGood(“user-123”); // OK! isVip は自動的に false になる
—
5. 実践:どちらを使うべき?迷ったときの判断基準
「じゃあ、普段のコードではどちらを使えばいいの?」と迷ったときは、以下のシンプルなフローチャートを参考にしてください。
[関数で受け取る引数について考える]
│
▼
Q. 値が渡されなかった時の「明確なデフォルト値(フォールバック)」が存在するか?
│
┌───────┴───────┐
YES NO
│ │
▼ ▼
【デフォルト値を使う】 【オプショナル引数を使う】
(例: page = 1) (例: middleName?: string)
内部処理がシンプルに! 「値が存在しない」という事実自体に
意味がある場合に最適!
- デフォルト値(`=`)が適している例:
- 検索一覧のページ番号(未指定なら `1` ページ目)
- タイムアウト時間(未指定なら `3000` ms)
- ユーザーの表示名(未指定なら `”名無し”`)
- オプショナル引数(`?`)が適している例:
- ユーザーのミドルネーム(無い人にはデフォルト値の割り当てようがない)
- ログ出力時の追加詳細データ(データが無いこと自体が正常な状態)
—
まとめ:ここをクリアすればTypeScriptの基本はバッチリマスター!
今回は、関数引数における「オプショナル(`?`)」と「デフォルト値(`=`)」の型推論と挙動の違いを解説しました。
1. `?` (オプショナル) は「内部に `undefined` を許容する」。使うときは型ガードが必要。
2. `=` (デフォルト値) は「内部から `undefined` を排除する」。JavaScriptが自動で補完してくれるため、内部では純粋な型として扱える。
この違いを正しく理解してコードが書けるようになると、無駄な `if (x !== undefined)` のようなチェックを減らすことができ、安全で美しいTypeScriptコードが書けるようになります。
焦らず一歩ずつ進んでいきましょう。型システムの挙動が手に取るように解る感覚、ぜひ楽しんでくださいね!応援しています!