こんにちは!TypeScriptの世界へようこそ。頼れる先輩として、皆さんがこの素晴らしい言語をマスターできるよう、一歩一歩ナビゲートしていきますね。
プログラミングを学んでいると、誰もが一度は遭遇する「画面が真っ白になる恐怖のバグ」があります。その主犯格が、「値が存在しない状態」を無理に操作しようとして発生するエラーです。JavaScriptでいう `TypeError: Cannot read properties of undefined` ですね。
かつて多くの開発者を悩ませてきたこの問題に対して、TypeScriptは強力な盾を用意してくれました。それが、今回ご紹介する `strictNullChecks`(厳密なnullチェック) という機能です。
「コンパイラに細かく注意されて窮屈そう…」と思うかもしれません。でも、実は逆なんです。この機能を有効にすることで、TypeScriptが「そこ、もしかしたら空っぽかもしれませんよ!」と、実行前に裏でこっそり教えてくれるようになります。
今回は、`null` と `undefined` の本質的な違いから、`strictNullChecks` がどのように私たちの開発体験を劇的に向上させてくれるのか、エンジニアの脳内をトレースするように優しく解説します。ここをクリアすれば、TypeScriptの基本はバッチリマスターできますよ!
—
1. 「値がない」を表す2つの容疑者:`null` と `undefined`
まずは基本のキとして、JavaScript/TypeScriptにおける「値がない」を表す2つの状態を整理しましょう。
これらは似て非なるものです。現実世界に例えると、とてもイメージしやすくなります。
| 値 | 現実世界でのイメージ | 意味合い |
| :— | :— | :— |
| `undefined` | コップを置く場所すら決まっていない | 「未定義」。変数に何も代入されていない初期状態。 |
| `null` | コップはあるけれど、中身は空っぽ | 「意図的な空値」。値がないということを、あえて明示している状態。 |
コードで見る違い
let userAddress: string | undefined;
// まだ住所情報が「定義すらされていない(undefined)」状態
let middleName: string | null = null;
// この人にはミドルネームが「存在しない(null)」ことが明示されている状態
この2つの値は、何も対策をしないと、プログラムの実行時(ランタイム)に牙をむきます。中身が空っぽのコップから、存在しないジュースを飲もうとするようなコードを書くと、プログラムは即座にクラッシュしてしまうのです。
—
2. `strictNullChecks: false` がもたらす「静かなる崩壊」
TypeScriptを導入していても、この `strictNullChecks` が `false`(無効)になっていると、かつての暗黒時代のままで開発することになります。
例えば、以下のようなコードを見てみましょう。
// strictNullChecks: false の世界
function greet(name: string) {
// name は string 型なので、絶対に大文字に変換できるはず…?
console.log(`Hello, ${name.toUpperCase()}!`);
}
// しかし、引数に null を渡せてしまう!
greet(null);
なぜこれが問題なのか?
`strictNullChecks` が `false` のとき、TypeScriptは 「`null` も `undefined` も、すべての型の仲間(サブタイプ)」 として扱ってしまいます。
つまり、`string` 型と宣言された変数に、しれっと `null` を代入できてしまうのです。
【strictNullChecks: false のイメージ】
[ string 型の部屋 ]
├─ “Alice”
├─ “Bob”
├─ null <-- 自由に出入りできてしまう!
└─ undefined <-- 自由に出入りできてしまう!
コンパイラは「問題なし!」とスルーしますが、いざ実行(ランタイム)すると、`null.toUpperCase()` が呼び出され、「そんな関数はありません!」とエラーを吐いてシステムがクラッシュします。
せっかくTypeScriptを使っているのに、これではもったいないですよね。
—
3. `strictNullChecks: true` の魔法:コンパイル時の鉄壁の防御
では、設定を `true` にしてみましょう。
設定は、プロジェクトのルートにある `tsconfig.json` で行います(`”strict”: true` を有効にしている場合は、自動的に `true` になっています)。
{
“compilerOptions”: {
“strictNullChecks”: true
}
}
これを有効にした瞬間、TypeScriptコンパイラの目の色が変わります。
先ほどのコードをもう一度見てみましょう。
// strictNullChecks: true の世界
function greet(name: string) {
console.log(`Hello, ${name.toUpperCase()}!`);
}
// コンパイルエラー!
// エラー: 型 ‘null’ の引数を型 ‘string’ のパラメータに割り当てることはできません。
greet(null);
今度は実行する前に、コードを書いたその場でエディタが赤線を引いて警告してくれます。
【strictNullChecks: true のイメージ】
[ string 型の部屋 ] [ null 型の部屋 ]
├─ “Alice” └─ null
└─ “Bob”
▲
└─ [厳重な警備]:null や undefined の立ち入りを禁止!
`string` の部屋から `null` が完全に排除されました。これにより、「`string` 型として定義された変数には、絶対に、確実に、100%文字列が入っている」ということがコンパイル時に保証されます。これがどれほど開発者を安心させてくれるか、計り知れません。
—
4. 実践!コンパイラと対話しながら `null/undefined` を手懐ける3つのアプローチ
「でも先輩、現実のアプリでは『ユーザーがログインしていない(null)』とか『検索結果がない(undefined)』という状況は普通にありますよね? どう書けばいいんですか?」
その通りです!値が空になる可能性をスマートに扱うために、TypeScriptでは主に3つのアプローチを使います。コンパイラと会話するようにコードを書いていきましょう。
アプローチ1:早期リターン(ガード節)による「型ガード」
最も王道で、最も安全な方法です。
TypeScriptのコンパイラはとても賢く、コードの流れを上から順に解析しています。これをコントロールフロー分析(Control Flow Analysis)と呼びます。
function printUserCity(user: { name: string; city: string | null }) {
// 1. まず最初に null の可能性をチェックして、処理を終わらせる(ガードする)
if (user.city === null) {
console.log(`${user.name}さんは住所を登録していません。`);
return; // ここで処理が終了
}
// 2. ここを通過したということは、絶対に null ではない!
// コンパイラが自動的に「city は string 型だ」と推論(Narrowing: 絞り込み)してくれます。
console.log(`${user.name}さんの住所は ${user.city.toUpperCase()} です。`);
}
「もし `null` だったらここで返す」と書くだけで、それ以降の行では `user.city` が自動的に `string` 型に変化します。これを型の絞り込み(Narrowing)と呼びます。コンパイラが私たちの書いたロジックを理解してくれている証拠です。
—
アプローチ2:オプショナルチェイニング(`?.`)とNull合体演算子(`??`)
モダンなTypeScript/JavaScriptで最も愛されている、スタイリッシュな書き方です。
type User = {
name: string;
profile?: {
bio: string | null;
};
};
function getBio(user: User): string {
// 1. 「?.」で安全にプロパティをたどる(途中で undefined があればそこで止まる)
// 2. 「??」で、もし左側が null または undefined だったら、右側のデフォルト値を返す
return user.profile?.bio ?? “自己紹介は未登録です。”;
}
// 実行例
console.log(getBio({ name: “Alice” })); // -> “自己紹介は未登録です。”
- `user.profile?.bio` : `profile` が存在しなければ、それ以上進まずに `undefined` を返します。
- `?? “自己紹介は未登録です。”` : 左側の評価結果が `null` や `undefined` だった場合に、右側の `”自己紹介は未登録です。”` を採用します。
一行で安全かつスッキリと書けるため、実際の現場でも頻出するパターンです。
—
アプローチ3:非nullアサーション(`!`)の誘惑と付き合う方法
時々、コンパイラに対して「うるさい!ここには絶対値が入っているんだ!」と強制的に黙らせたくなることがあります。その時に使うのが `!`(非nullアサーション)です。
※ただし、これは「禁断の果実」ですので注意が必要です。
function processValue(value: string | null) {
// コンパイラを強制的に黙らせる「!」
// 「絶対に null じゃないからエラーを出さないで!」と開発者が誓約する
const upper = value!.toUpperCase();
console.log(upper);
}
// しかし、もし嘘をついて null を渡すと…?
processValue(null); // -> ランタイムエラー!
`!` を使うと、コンパイラはチェックをやめてしまいます。しかし、実行時に値が `null` だったら、当然のようにクラッシュします。
型安全のセーフティネットを自分から切り裂くような行為ですので、「テストコードで確実に値が入っていると分かっている場合」などを除き、原則として使用は避けましょう。 代わりに「アプローチ1」や「2」を使って、コンパイラに優しく証明してあげるのがスマートなエンジニアの作法です。
—
まとめ:TypeScriptの優しさに身を委ねよう
今回学んだことを振り返ってみましょう。
1. `null` は「意図的な空」、`undefined` は「未定義」。どちらも値がない状態を表す。
2. `strictNullChecks: true` は、この2つが一般の型(`string` など)に紛れ込むのを防ぐバリア。
3. コンパイラがエラーを出すのは、実行時にバグになる箇所を先回りして教えてくれている優しさ。
4. 「型ガード(`if`文)」や「オプショナルチェイニング(`?.` / `??`)」 を使って、安全に型を絞り込む。
最初はコンパイルエラーが出ると「TypeScriptに怒られた…」と感じるかもしれません。でも、それは違います。コンパイラは、未来のユーザーがアプリを使っていて困る前に、あなたにそっと耳打ちしてくれている頼もしい相棒なのです。
この `strictNullChecks` を味方につければ、あなたの書くコードの堅牢性は見違えるほど向上します。自信を持って、安全なコードを紡いでいってくださいね。
「ここをもっと詳しく知りたい!」「このエラーはどう解決すればいい?」といった疑問があれば、いつでも聞いてください。いつでも応援していますよ!