TypeScriptで null を扱おうとしたのに判定が思ったように効かず、Object is possibly 'undefined'. の赤線が消えなくて手が止まっていませんか。

nullをチェックしてるのに「undefinedかも」って赤線が消えないの、なんで?
nullとundefined、そもそも何が違うの…どっちを使えばいいのか分からない。
! を付ければ赤線は消えるけど、これ本当に安全なのかな?

TypeScriptのnullとundefinedは似て見えますが、意味も、発生する場面も違います。この記事では両者の違いから、判定の書き分け、?.??、そして非nullアサーション(!)まで一通り整理し、目の前の値に対して「どの書き方を選べばいいか」を自分で判断できる状態を目指します。なお解説はすべて strictNullChecks が有効な前提で進めます。

この記事は次のような方におすすめです。

この記事はこんな人におすすめ!
  • nullとundefinedの違いが曖昧なまま実装している方
  • 「possibly null / undefined」のエラーの消し方が分からない方
  • == null?.??! を、なんとなくで使い分けている方
  • 非nullアサーション(!)を付けていいか毎回迷う方

読み終えるころには、null/undefinedのエラーが出た理由を説明でき、その場に一番合った判定の書き方を選べるようになります。

それでは、順を追って詳しく見ていきましょう!

nullとundefinedは何が違うのか

TypeScript(およびJavaScript)には「値が無い」を表す型が2つあります。おおまかには、nullは「意図的に空である」ことを表す値、undefinedは「まだ定義・代入されていない」ことを表す値として使い分けられます。ただし言語仕様がこの意味を強制するわけではなく、どちらも自由に代入できるため、実際の意味づけはコードの書き方に依存します。

両者の違いを5つの観点で並べると次のようになります。

観点 null undefined
意味 意図的に「空」を入れた状態 未定義・未代入の状態
自然発生 基本的にしない(代入して初めて現れる) する(未初期化・戻り値なし等で自動的に生じる)
typeof の結果 "object" "undefined"
JSON.stringify キーと値が残る キーごと消える
推奨方針 APIやDBで「無い」を明示したいときに使う 言語上、自然に生じる値として扱う

typeof の挙動は次のとおりで、typeof null"object" を返すのは歴史的経緯による有名な癖です。

console.log(typeof null);      // "object"
console.log(typeof undefined); // "undefined"

JSON化したときの差も実用上よく効いてきます。

console.log(JSON.stringify({ a: null, b: undefined }));
// {"a":null}   ← b のキーごと消える

このように、undefinedはキーごと消え、nullは null として残るため、「サーバに送ったら値が消えた」といった挙動の原因切り分けにも役立ちます。どの操作でundefinedが現れるのか、そしてnullをどこで使うのかを押さえると、原因の切り分けがしやすくなります。

undefinedが自然に発生する場面

undefinedは、こちらが代入しなくても言語側で自然に発生する値です。代表的なのは「まだ値がない」状態が生まれる次の4パターンです。

// 1. 初期化していない変数
let x: number | undefined;
console.log(x); // undefined

// 2. 存在しないプロパティへのアクセス
const obj: { name?: string } = {};
console.log(obj.name); // undefined

// 3. return を持たない関数の戻り値
function noReturn() {}
console.log(noReturn()); // undefined

// 4. 配列の範囲外アクセス
const arr = [10, 20];
console.log(arr[5]); // undefined

いずれも「そこに値がまだ無い」状況で、明示的に何かを入れた覚えがなくてもundefinedが返ってきます。

なかむぅ
なかむぅ
プロパティを省略可能にする ? の書き方や ?. でのアクセス設計をまとめて知りたい方は、optionalプロパティと ?. の使い方も読んでみてください。
TypeScriptのoptionalとは?'?'と'?.'の使い分けで迷わないようにしようTypeScriptのoptionalを「何となく」使っていて、? を付ける場所や ?. との関係があいまいなまま書いている、という人は...

nullを明示的に使う場面

undefinedと違い、nullは基本的に開発者が明示的に代入しない限り現れません。そのため使いどころは、「値が存在しないこと」をこちら側から意図的に表現したい場面が中心になります。

典型例は、APIレスポンスやデータベースの「該当データなし」を表すケースです。

type User = {
  id: number;
  nickname: string | null; // 未設定なら null で「無い」を明示
};

const user: User = { id: 1, nickname: null };

string | null のようにユニオンで書くと、「文字列が入るか、意図的に空か」を型として表現できます。undefined(未定義)とは出どころが違い、nullは「無い状態を明示した」というシグナルとして読めるわけです。

なかむぅ
なかむぅ
nullで「無い」を表す以外に、失敗や不在を型で安全に表現する方法を知りたい方は、Result型による型安全なエラー処理もどうぞ。
【TypeScript】Result型でtry-catchを型安全に扱うTypeScriptのResult型を使うと、try-catchでは型に表れなかった「失敗の可能性」をコンパイラに見せられます。catc...

strictNullChecksがnull/undefinedの扱いを決める

「同じコードなのに、人によってnullで赤線が出たり出なかったりする」——その分かれ目が strictNullChecks です。この設定がオフのときは、すべての型が暗黙的にnull/undefinedを受け入れます。オンのときは、| null| undefined を型に明示しない限り、null/undefinedを代入できなくなります。

// strictNullChecks: false のとき
let name: string = null; // エラーにならない(暗黙に許容される)

// strictNullChecks: true のとき
let name: string = null;
// 例:TS2322 Type 'null' is not assignable to type 'string'.

オンにすると、null/undefinedが入りうる値に無防備にアクセスした箇所も検出されます。

function getLength(s: string | null) {
  return s.length;
  // 例:TS2531 Object is possibly 'null'.
}

この挙動は tsconfig.json の設定に紐づきます。"strict": true を有効にすると、strictNullChecks など複数の厳格化オプションがまとめてオンになります。個別に "strictNullChecks": true だけを指定することもできます。TypeScript公式ドキュメントのTypeScript Handbook「The Basics」でも、strict系オプションを有効にした開発が推奨されています。

まとめると、null関連のエラーが出るかどうかはコードの正しさだけでなく、まずこの設定のオン/オフに左右されるということです。

この赤線を s as string のように型を書き換えて消してしまう人もいますが、それはnullチェックではなく型情報を上書きしているだけです。実際の値がnullのままなら、実行時には Cannot read properties of null がそのまま発生します。

なかむぅ
なかむぅ
as による型アサーションの使いどころと危険性を押さえたい方は、型アサーションasの注意点をあわせて確認してみてください。
TypeScriptのasとは?型アサーションの危険性と安全な代替TypeScriptのas(型アサーション)を実コードとtscのエラーで解説。書き方・キャストとの違い・なぜ危険かを示し、asを避ける型ガードやsatisfiesまで網羅。エラーを消すだけでなく型安全を守りたい人へ。...

strictNullChecksをオンにすべき理由と移行のコツ

既存プロジェクトでオンにすると大量のエラーが出るため尻込みしがちですが、それでもオンにする価値はあります。エラーの正体は「これまで見逃されていたnull/undefinedアクセスの候補」そのもので、実行時に Cannot read properties of null などで落ちる前に、型の段階で気づけるようになるからです。

いきなり全部を厳格化するのが重い場合、"strict": true を一度に入れず、"strictNullChecks": true だけを先に有効化する進め方があります。初期対応でエラー件数を減らすには、次のような順序で臨むと負荷を分散できます。

  • まず影響範囲の狭いファイル・モジュールから型に | null / | undefined を明示していく
  • 頻出する箇所は早期returnや判定を1か所に集約し、下流の分岐を減らす
  • どうしても後回しにしたい箇所は局所的に切り出し、全体を止めない

一気に赤線をゼロにしようとせず、安全になった箇所から確実に積み上げるのが現実的な移行になります。

nullとundefinedを判定する方法

値がnullやundefinedかを判定するとき、基本の選択肢は3つあります。厳密に個別判定する === null / === undefined= が3つ)、両方をまとめて判定できる == null= が2つ)、そして値の有無をざっくり見る truthy 判定(if (value)です。

function check(v: string | null | undefined) {
  if (v === null) { /* null のときだけ */ }
  if (v === undefined) { /* undefined のときだけ */ }
  if (v == null) { /* null と undefined の両方 */ }
}

== null が両方に反応するのは、==(抽象等価比較)の仕様上、nullundefined が互いに等しいと扱われるためです。この挙動はMDNのMDN「等価性の比較と同一性」で説明されています。

一方 truthy 判定には落とし穴があります。

function ngCheck(v: number | null) {
  if (!v) {
    // v が 0 のときもここに入ってしまう
  }
}

if (v)0 や空文字 "" も「偽」とみなすため、「値は存在するが 0 や空文字である」ケースを誤って「無い」と判定してしまうのが典型的なバグ源です。数値や文字列に対して「null/undefinedかどうか」を見たいなら、truthy 判定は避けるべきです。

判定方法を一覧にすると次のように整理できます。

判定対象 注意点
x === null null のみ undefined は拾わない
x === undefined undefined のみ null は拾わない
x == null null と undefined の両方 意図的な使用に限れば安全
if (x) (truthy) 0・””・false 等も「無い」扱い 数値・文字列では誤判定の原因

== null でまとめて判定してよいのか

== null は「緩い比較だから避けるべき」と言われがちですが、null/undefinedの判定に限れば、意図して使う分には安全です。通常のアプリケーションで扱う値であれば、x == nullxnull または undefined のときだけ true になり、0"" を巻き込むことはありません。

console.log(0 == null);         // false
console.log("" == null);        // false
console.log(null == undefined); // true
console.log(undefined == null); // true

== を全面禁止するESLintの eqeqeq ルールを使っている場合でも、null との比較だけは許可する設定にできます。

// .eslintrc の rules
"eqeqeq": ["error", "always", { "null": "ignore" }]

この設定なら、== null だけを例外的に許しつつ、他の緩い比較はエラーにできます。「null/undefinedをまとめて弾く」という明確な意図がある箇所に限れば、== null は簡潔で読みやすい選択肢になります。

null/undefinedかもしれない値へ安全にアクセスする

「nullかもしれない値」に判定を書かずにアクセスしたいときは、オプショナルチェーン ?. とNullish合体演算子 ?? が使えます。?. は途中の値がnull/undefinedならそこで評価を止めて undefined を返し、?? は左辺がnull/undefinedのときだけ右辺の値を採用します。

type User = { profile?: { name?: string } };
const user: User = {};

console.log(user.profile?.name);
// undefined(profile が無くてもエラーにならず undefined になる)

const name = user.profile?.name ?? "名無し";
console.log(name); // "名無し"(?? で既定値を補う)

ここで注意したいのが ??|| の違いです。|| は「偽っぽい値」全般で右辺を採用するため、0"" を意図せず既定値へ置き換えてしまいます。

演算子 右辺を採用する条件 0 / “” の扱い
?? 左辺が null または undefined のときだけ そのまま左辺の 0 / “” が残る
|| 左辺が偽っぽい値(0・””・false 等)のとき 0 / “” も右辺に置き換わる
const count = 0;
console.log(count ?? 10); // 0  (0 は有効な値として残る)
console.log(count || 10); // 10 (0 が偽なので右辺が採用される)

つまり、「null/undefinedのときだけ既定値にしたい」なら ??、偽とみなされる値全般で既定値にしたいなら || という使い分けになります。?.?? の挙動はMDNのMDN「Nullish coalescing operator」MDN「Optional chaining」に詳しく記載されています。

なかむぅ
なかむぅ
?. の連鎖や、宣言側で ? を付けた設計まで踏み込みたい方は、optionalプロパティと ?. の使い方が参考になります。

非nullアサーション(!)と型ガードで型を絞り込む

!(非nullアサーション)を値の後ろに付けると赤線は消えますが、これは要注意です。! は型からnull/undefinedを外すだけで、実行時に値をチェックするわけではありません。型チェッカー上の警告を消しているだけなので、実際にnull/undefinedが入っていれば実行時にそのまま落ちます。

function getName(user?: { name: string }) {
  return user!.name; // 型上は user が必ず存在する扱いになる
}

getName(); // 実行時:TypeError: Cannot read properties of undefined (reading 'name')

型エラーは消えているのに、実行するとクラッシュします。これが ! を安易に使うことの危険性です。代わりに、値を実際に確認してから絞り込む書き方をとれば、型も実行時も両方安全になります。

function getName(user?: { name: string }) {
  if (user == null) return "名無し"; // 早期returnで null/undefined を除外
  return user.name; // ここでは user は存在すると型でも保証される
}

早期returnや if (x != null) による絞り込みのほか、判定を関数に切り出す型ガードも有効です。TypeScriptは条件分岐の結果を型に反映するため、! を書かなくても分岐の内側では自動的にnull/undefinedが除かれた型として扱えます。この仕組みは TypeScript 2.0 で導入された機能で、公式のTypeScript 2.0 Release Notes「Non-null assertion operator」でも ! は「本当にnullでないと保証できる場合の手段」として位置づけられています。! は最後の手段、まずは判定で絞り込むのが安全な原則です。

NonNullableで型からnull/undefinedを除く

ここまでは「値」を絞り込む話でしたが、「型」レベルでnull/undefinedを外したいこともあります。そのときは組み込みのユーティリティ型 NonNullable<T> が使えます。

type MaybeString = string | null | undefined;
type OnlyString = NonNullable<MaybeString>; // string

! や型ガードが「ある値について、この地点ではnullでない」と絞り込むのに対し、NonNullable<T> は型定義そのものからnull/undefinedを取り除く役割です。値の絞り込みと型の加工という、対象が異なる操作だと整理しておくと理解しやすくなります。

無い値をどう判定するかの判断フロー

ここまでの手段を、目の前の値に対して「どれを選ぶか」という流れにまとめます。やりたいことを起点に選ぶと迷いにくくなります。

  • 値にアクセスだけしたい(途中がnull/undefinedなら止めたい)
    ?.
  • null/undefinedのとき既定値を入れたい
    ??
  • null/undefinedかで処理を分岐したい
    == null(両方まとめる)または型ガード
  • 絶対にnullでないと保証できる(ごく限られた場面)
    ! を最小限だけ

たとえば user.profile?.name ?? "名無し" は「アクセス(?.)+既定値(??)」の組み合わせ、if (user == null) return は「分岐」、user!.name は「保証できるときだけの最小限」という具合に、それぞれ典型的な用途が異なります。判定の書き方に正解が1つあるのではなく、目的ごとに最適な手段が違う——この対応関係を押さえておけば、次に赤線が出ても慌てず選べます。

【付録】さらに学びを深めるためのリソース


さらにTypescriptの学習を進めたい方のために、いくつかのリソースを紹介します。
これらのリソースを活用することで、TypeScriptの型システムについてより深い知識を得ることができるでしょう。

おすすめの書籍

ゼロからわかる TypeScript入門


技術評論社から出版されている「ゼロからわかる TypeScript入門」は、プログラミング初心者や本職プログラマーではない方を主な対象にした入門書です。

変数・条件分岐・ループといった基本から、クラスやインターフェース、モジュールまで段階的に学べる構成になっています。最終章ではWeb APIとJSONを使った非同期Webアプリの作成も体験できるので、「実際に動くものを作る」ところまで到達できます。

プロを目指す人のためのTypeScript入門


技術評論社の「プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで」、通称 ブルーベリー本 です。
JavaScriptの仕様とTypeScript独自の機能を両方押さえつつ、リテラル型・ユニオン型・keyof型・ジェネリクスなど、高度な型表現まで踏み込んで解説しています。TypeScriptの型システムの表現力を本格的に学べる一冊です。

オンラインで参照できる公式ドキュメント

TypeScript公式ハンドブック


https://www.typescriptlang.org/docs/
TypeScriptの公式ドキュメントです。
intersection型を含む、すべての型システムの機能について詳細な説明があります。

TypeScript Deep Dive


https://basarat.gitbook.io/typescript/
TypeScriptの深い部分まで掘り下げて解説しているオンラインブックです。
無料で読むことができ、intersection型についても詳しく説明されています。

TypeScriptの学習は終わりがありません。
新しい機能が常に追加され、より良い書き方が発見されています。
継続的に学習を続けることで、より良いTypeScriptプログラマーになれるはずです。


まとめ – nullとundefinedを安全に扱う要点

この記事の要点をまとめます。

  • nullは「意図的に空」、undefinedは「未定義・未代入」で、typeofやJSON化の挙動も異なる
  • null/undefinedのエラーは、まず strictNullChecksのオン/オフで扱いが決まる(本記事はオン前提)
  • 判定は個別なら === null / === undefined両方まとめるなら == null が簡潔
  • アクセスは ?.、既定値は ??|| と違い 0 や “” を残す)で判定を書かずに済ませられる
  • !(非nullアサーション)は実行時チェックをしないため最小限にとどめ、早期returnや型ガードでの絞り込みを優先する

null/undefinedは「なんとなく」で書くと詰まりやすい一方、意味と手段を対応づけて選べば、赤線にもバグにも先回りして対応できます。

なかむぅ
なかむぅ
判定をさらに進めて、条件分岐で型を安全に絞り込む書き方を深めたい方は、型ガードでの型の絞り込みへどうぞ。
TypeScriptの型ガードとは?4つの判定方法と使い分けTypeScriptの型ガードとは何かを基礎から整理。typeof・instanceof・in・ユーザー定義型ガード(is)を実コードと出力で解説し、自作の落とし穴と、対象別にどれを選ぶかの判断基準まで一気にわかります。...
なかむぅ
なかむぅ
宣言側で ? を付けるoptionalプロパティと ?. の使い方を体系的に押さえたい方は、optionalプロパティと ?. の使い方が参考になります。
TypeScriptのoptionalとは?'?'と'?.'の使い分けで迷わないようにしようTypeScriptのoptionalを「何となく」使っていて、? を付ける場所や ?. との関係があいまいなまま書いている、という人は...

※本記事の本文案はAIを活用して作成していますが、記載している内容およびコードは筆者が実際に調査、検証・実行し、内容の正確性を確認した上で公開しています。