こんにちは、かつコーチです。
前回は、オプショナルプロパティ(?)と readonly の使い方について解説しました。
今回は、TypeScriptを実務で使ううえで避けて通れない「null と undefined の扱い方」を取り上げます。
「Cannot read properties of undefined」というエラーメッセージを見たことがある人は多いと思いますが、TypeScriptの strictNullChecks を理解すると、このエラーの多くをコンパイル時点で防げるようになります。
strictNullChecksとは?
null・undefinedを許容するかどうかの設定
strictNullChecks は、TypeScriptのコンパイラオプションのひとつです。
tsconfig.json で次のように設定します。
{
"compilerOptions": {
"strict": true
}
}
strict: true を指定すると、strictNullChecks を含む複数の厳格なチェックがまとめて有効になります。
このオプションがオフの場合、TypeScriptはどんな型の変数にも null や undefined を代入できてしまいます。
// strictNullChecksがオフの場合
let name: string = null; // エラーにならない
オンにすると、明示的に許可しない限り null や undefined を代入できなくなります。
// strictNullChecksがオンの場合
let name: string = null; // ❌ エラー:Type 'null' is not assignable to type 'string'
なぜstrictNullChecksが重要なのか
JavaScriptの実行時エラーの中でも、特に多いのが「値がないのにあるものとして扱ってしまう」ことによるエラーです。
const user = getUser(); // ユーザーが見つからずnullが返ってくる可能性がある
console.log(user.name); // TypeError: Cannot read properties of null
strictNullChecks をオンにしておくと、こういった「値がない可能性」をコンパイラが検出し、実行前に気づかせてくれます。
私が新卒の頃、strictNullChecks をオフのまま開発を続けていたプロジェクトで、本番環境で undefined のプロパティを読もうとしてサービスが落ちたことがありました。
その一件以来、新しいプロジェクトを立ち上げるときは必ず strict: true を最初から有効にするようにしています。
null許容型の書き方
ユニオン型でnullやundefinedを表現する
strictNullChecks がオンの状態で「この値は null になる可能性がある」と伝えたい場合は、ユニオン型を使います。
function findUser(id: number): string | null {
if (id === 1) {
return "かつコーチ";
}
return null;
}
const name = findUser(2);
console.log(name.length); // ❌ エラー:nullの可能性があるため直接使えない
string | null という型にすることで、「文字列が返ってくることもあれば、見つからずに null が返ってくることもある」という関数の意図を型として明示できます。
このままではエラーになるため、使う側で null かどうかをチェックする必要があります。
const name = findUser(2);
if (name !== null) {
console.log(name.length); // ここまで来ればstring型として扱える
} else {
console.log("ユーザーが見つかりませんでした");
}
このように、条件分岐によって型が絞り込まれる仕組みを「型ガード」と呼びます。
Before/Afterで見るnullチェックの書き方
チェックを省略してエラーになるパターン
❌ Before:nullチェックを省略してしまう
interface User {
id: number;
name: string;
address: string | null;
}
function printAddressLength(user: User) {
console.log(user.address.length); // ❌ エラー:addressはnullの可能性がある
}
address は string | null 型なので、null の場合に .length を呼ぶとランタイムエラーになります。
TypeScriptはこの危険性をコンパイル時点で検出し、そのままではコンパイルを通しません。
✅ After:条件分岐で安全にチェックしてから使う
interface User {
id: number;
name: string;
address: string | null;
}
function printAddressLength(user: User) {
if (user.address !== null) {
console.log(user.address.length); // string型として扱える
} else {
console.log("住所は未登録です");
}
}
条件分岐を挟むことで、TypeScriptが「このブロックの中では address は string として扱ってよい」と判断してくれます。
オプショナルチェイニングとNull合体演算子
?.(オプショナルチェイニング)
プロパティを安全に辿りたいときは、?.(オプショナルチェイニング)が便利です。
interface User {
profile: {
bio: string;
} | null;
}
function printBio(user: User) {
console.log(user.profile?.bio); // profileがnullならundefinedを返す(エラーにならない)
}
user.profile が null の場合、そこで処理を止めて undefined を返してくれるため、if 文でいちいちチェックしなくても安全にアクセスできます。
??(Null合体演算子)
null や undefined だった場合に、代わりのデフォルト値を設定したいときは ?? を使います。
function getDisplayName(name: string | null): string {
return name ?? "ゲスト";
}
console.log(getDisplayName("かつコーチ")); // "かつコーチ"
console.log(getDisplayName(null)); // "ゲスト"
似たような目的で使われる ||(論理OR)との違いに注意が必要です。
const count = 0;
console.log(count || 10); // 10(0はfalsyなので10になってしまう)
console.log(count ?? 10); // 0(nullでもundefinedでもないので0のまま)
|| は 0 や空文字のような「falsyな値」もすべて置き換えてしまいますが、?? は null と undefined の場合だけ置き換えます。
私はこの違いに気づかず、「在庫数0件」を表示したいのに || を使ったせいでデフォルト値が表示されてしまう、という不具合を出してしまったことがあります。
「null/undefined かどうかだけを判定したい」場合は、?? を選ぶのが安全です。
nullとundefinedの使い分け方
実務でのおすすめの使い分け
TypeScriptでは null と undefined の両方を扱えますが、実務では使い分けの指針を決めておくとチーム全体でコードが読みやすくなります。
| 状況 | おすすめの値 | 理由 |
|---|---|---|
| プロパティが「まだ設定されていない」 | undefined(またはオプショナル ?) | JavaScriptの初期値の慣習に近い |
| 「意図的に空である」ことを示したい | null | 「値がない」ことを明示的に表現できる |
| APIから返ってくる「見つからなかった」結果 | null | 多くのAPI設計・DB設計の慣習に合わせやすい |
どちらか一方に統一するチームも多く、たとえば「関数の戻り値が見つからない場合は必ず null」「オブジェクトの未設定プロパティは undefined」のようにルールを決めておくと、型の見通しがよくなります。
まとめ
この記事のポイント
strictNullChecks(strict: true)を有効にすると、null・undefinedの扱いをコンパイル時にチェックできるnullを許容する値はstring | nullのようにユニオン型で明示する- 条件分岐(型ガード)を使うと、
nullでないことが保証された状態で値を扱える ?.(オプショナルチェイニング)と??(Null合体演算子)を使うと安全に値を扱いやすい??はnull/undefinedのみを、||はfalsy全般を置き換える点に注意する
次に読むべき記事
次回は、TypeScriptの型システムの応用への橋渡しとなる「ジェネリクス」の入門編を解説します。
→ 次の記事:ジェネリクス入門:型を引数のように扱う
