こんにちは!TypeScriptの型システムを旅する皆さん、チーフアーキテクトの私です。
今回は、関数における「デフォルト引数」と、Null合体演算子とも呼ばれる「Nullish Coalescing(`??`)」の組み合わせが生み出す、型推論の深淵についてお話しします。
「引数が渡されなかったときのデフォルト値」を設定する方法はいくつかありますが、実はTypeScriptの型推論においては、書き方ひとつでコンパイラが受け取る型(そして私たち開発者が受ける恩恵)がガラリと変わるんです。
ここをクリアすれば、TypeScriptの型推論のメカニズムが一枚皮を剥けたように理解できるようになりますよ。さあ、一緒にその仕組みを紐解いていきましょう!
—
1. 基礎知識:デフォルト引数 vs Nullish Coalescing (`??`)
まずは、私たちがよく使う「値がなかったときのフォールバック(初期値)」の2つのアプローチを確認しておきましょう。
① デフォルト引数(Default Parameters)
ES6(JavaScript ES2015)から導入された、最も標準的な構文です。引数が `undefined` の場合にデフォルト値が適用されます。
function greet(name: string = “ゲスト”) {
return `こんにちは、${name}さん!`;
}
greet(); // “こんにちは、ゲストさん!”
greet(undefined); // “こんにちは、ゲストさん!”
② Nullish Coalescing (`??` 演算子)
左側の値が `null` または `undefined` の場合に、右側の値を採用する演算子です。関数の中身でフォールバックを書きたいときによく使われます。
function greet(name?: string) {
const actualName = name ?? “ゲスト”;
return `こんにちは、${actualName}さん!`;
}
一見すると「どちらを使っても同じでは?」と思いますよね。しかし、ここに 「型推論の優先順位」 というTypeScriptならではの深いテーマが隠されているのです。
—
2. 陥りやすい罠:`null` が混在したときの型推論の挙動
実務の現場(特にAPIから取得したデータや、古いJavaScriptとの連携コード)では、引数に `undefined` だけでなく `null` が入ってくることがよくあります。
ここで、以下の2つのコードを見比べてみてください。どちらが安全で、TypeScriptの恩恵を最大限に受けられるでしょうか?
パターンA:引数の型に `null | undefined` を許容し、関数内で `??` を使う
// 引数自体は null も受け取れるように広くとる
function processUser(id: string | null | undefined) {
// nullish coalescing でデフォルト値を設定
const resolvedId = id ?? “ANONYMOUS_ID”;
// ここでの resolvedId の型は?
// 答え: string に絞り込まれる!
console.log(resolvedId.toUpperCase());
}
パターンB:デフォルト引数に `null` を渡そうとする
// デフォルト引数に null を設定しようとする(一見お洒落に見えるけど…?)
function processUser(id: string = “ANONYMOUS_ID”) {
// …
}
// 呼び出し側
processUser(null);
// ❌ 怒られる!: 型 ‘null’ の引数を型 ‘string | undefined’ のパラメータに割り当てることはできません。
> ここがポイント!
> デフォルト引数(`= “…”`)は、「引数が `undefined` のとき」にのみ発動します。JavaScriptの仕様上、明示的に `null` が渡された場合、デフォルト値は発動せずに `null` のまま素通りしてしまいます。そのため、TypeScriptは「`null` が入る可能性があるなら、`string` 型のつもりで書いた関数内でエラーになるかもしれない」と懸念し、厳格に型エラーを出して守ってくれるのです。
—
3. 型推論の優先順位と、私たちが選ぶべきベストプラクティス
TypeScriptのコンパイラは、コードの意図を次のような優先順位で解釈しています。
1. 明示的な型注釈 (`: string | null`): 開発者が「これを受け入れる」と宣言した範囲を最優先する。
2. デフォルト引数 (`= “default”`): 引数が省略された(`undefined` だった)場合の型を自動的に `string`(デフォルト値の型)に収束させる。
3. Nullish Coalescing (`??`): 実行時における安全網として機能し、型ガードの役割も果たして `null | undefined` を削ぎ落とす。
この優先順位を踏まえた上で、実務で私たちが採用すべき黄金のベストプラクティスを伝授します。
ベストプラクティス:型定義は広く、ガードは `??` でスマートに
APIレスポンスなど、外部から何が飛んでくるか分からない境界線(Boundary)では、引数の型を `null | undefined` 許容にしつつ、内部で `??` を使って安全に型を確定させるのが最も堅牢です。
type Config = {
retries?: number | null;
};
// 良い例:引数でガチガチに縛らず、?? で優しく受け止める
function initializeApp(config: Config) {
// 実行時の null / undefined を安全にデフォルト値に倒しつつ、
// 返り値の型を「確実に number」へと昇華させる
const retries: number = config.retries ?? 3;
console.log(`リトライ回数: ${retries}回で初期化します`);
}
// 呼び出し側も安心
initializeApp({ retries: null }); // OK! ちゃんとデフォルトの 3 にフォールバックされる
initializeApp({}); // OK! undefined なので 3 にフォールバックされる
この書き方の美しいところは、「呼び出し側に余計なプレッシャーを与えない(`null` を渡しても怒られない)」かつ、「関数内部では `null | undefined` の存在を完全に忘れて純粋な `number` としてビジネスロジックに集中できる」という点です。
—
まとめ
今回は、関数の引数におけるデフォルト引数と Nullish Coalescing の挙動、そして型推論の優先順位について解説しました。
- デフォルト引数(`=`)は `undefined` にのみ反応する。
- 外部からの入力などで `null` の混入が予想される場合は、引数の型を広く取り、関数内で `??`(Nullish Coalescing)を使って安全にフォールバックさせる。
- このアプローチにより、呼び出し側の利便性と、関数内部の型安全性を高い次元で両立できる。
ここをクリアできれば、TypeScriptの型システムが「ただの厳しいお巡りさん」ではなく、「最高の相棒」に見えてくるはずです。
日々のコーディングで、ぜひこの知見を活かしてみてくださいね。それでは、次のレッスンでお会いしましょう!