【実務・中級編】 NonNullableによるnullとundefinedの排除 – TypeScript実践ガイド

こんにちは。フロントエンドチームのシニアアーキテクトです。

日々、モダンなWebアプリケーション開発でTypeScriptと格闘している君なら、一度はこんな絶望を味わったことがあるはずだ。「いや、絶対にここに値はあるはずなのに、なんでTypeScriptは`Object is possibly ‘null’`って怒るんだよ!」と。

APIから返ってくるデータ、ローカルストレージの値、あるいはDOMの要素取得。現場のコードは、しばしば「nullやundefinedの地雷原」だ。この地雷を華麗に、かつ安全に踏み抜かずに進むための強力な武器が、TypeScript標準のユーティリティ型 `NonNullable` である。

今回は、この`NonNullable`が内部でどう動き、実務の現場でどう使えばコードが劇的に美しくなるのか、俺の知見をすべて叩き込んでいこう。

—

1. `NonNullable` とは何か?(公式仕様の裏側)

まず基本のおさらいだ。`NonNullable`は、TypeScriptの組み込み型(Utility Types)の一つ。その名の通り、ジェネリクスに渡された型から `null` と `undefined` を綺麗さっぱり取り除く魔法のフィルターだ。

その実装は、TypeScriptの型定義ファイル(`lib.es5.d.ts`など)を覗けば一目瞭然、実はたったこれだけのコードで書かれている。

type NonNullable = T extends null | undefined ? never : T;

「……は?これだけ?」と思ったかい?そう、これだけだ。
ここで行われているのは、TypeScriptの条件付き型(Conditional Types)と分散条件付き型(Distributive Conditional Types)の合わせ技だ。

ユニオン型を `T` に渡すと、TypeScriptは内部で自動的にそれをバラバラに分解し、一つひとつの型に対して「お前、`null`か`undefined`か?」と問いかける。もし該当すれば `never`(「そんな型は存在しない=消滅させる」という意味)に置き換え、該当しなけらばそのまま残す。

この仕組みによって、混沌としたユニオン型から「無駄なノイズ」を完璧に排除できるというわけだ。

—

2. ブラウザとランタイムの現実、そしてTypeScriptの役割

ここで少し立ち止まって、ブラウザの裏側の話をしよう。
JavaScriptのランタイム(V8エンジンなど)にとって、`null` も `undefined` も、突き詰めれば「値が存在しない」ことを示すプリミティブな概念やメモリ上の番地に過ぎない。ブラウザは、実行時に「あ、これnullだわ、プロパティ読み取れないからTypeError吐こ」と、泥臭く動いているだけだ。

TypeScriptの型チェックは、このブラウザのランタイムエラーを、実行する前に静的にねじ伏せるためのものだ。

しかし、実務で厄介なのは「TypeScriptの型定義」と「実際のAPIレスポンスやDOMの状態」が乖離している瞬間だ。例えば、本当は絶対存在するDOM要素を取ろうとしているのに、型定義が `HTMLElement | null` になっているがために、その後の処理で毎回ガードを書かされる。

const header = document.getElementById(‘main-header’);
// headerがnullかもしれないので、毎回 ?. や if文を書く羽目になる
header.style.backgroundColor = ‘red’; // 怒られる

ここで `NonNullable` を正しく使えば、「このスコープに到達した時点で、この値は絶対にnull/undefinedではない」という強い意志をTypeScriptの型システムに伝えることができるんだ。

—

3. 実務で即コピペできる!実践コードパターン

百聞は一見にしかず。現場でよく遭遇するシチュエーションをベースに、`NonNullable` の実践的な使い方を見ていこう。

パターンA: フォームの入力値やAPIレスポンスのフィルタリング

例えば、ユーザーのIDリストの配列の中に、オプションとして `null` や `undefined` が混ざってしまっているケースだ。これをそのまま処理するとバグの元になる。

// APIから返ってきた、あるいはフォームから取得した生データ(nullが混ざっている)
type RawUserIds = (string | null | undefined)[];

// NonNullableを使って、各要素からnullとundefinedをパージした型を作る
type CleanUserIds = NonNullable[];
// 展開すると string[] になる!

function processUserIds(ids: RawUserIds) {
// 実行時(ランタイム)のフィルター
// ここでフィルタリングしても、従来の型推論だと配列の型が string | null のままのことがある
const validIds: CleanUserIds = ids.filter(
(id): id is NonNullable => id !== null && id !== undefined
);

// validIds は完全に string[] として扱えるため、安心して処理できる
validIds.forEach(id => {
console.log(id.toUpperCase()); // string型なので安心してメソッドが呼べる
});
}

ここでポイントなのは、ユーザー定義型ガード(`id is NonNullable`)と `NonNullable` を組み合わせるテクニックだ。これによって、ランタイムの安全性とコンパイル時の安全性が完璧に手をつなぐ。

パターンB: 設定オブジェクトのオプショナルプロパティを一括強制する

アプリケーションの初期化時、部分的な設定(Partial)を受け取るが、内部でデフォルト値をマージした後は「すべての設定が必ず揃っている状態」の型に変換したいときがある。

type AppConfig = {
theme: ‘light’ | ‘dark’ | null;
apiEndpoint: string | undefined;
timeout: number | null;
};

// すべてのプロパティから null と undefined を強制的に排除した型を作る
type ValidatedConfig = {
[K in keyof AppConfig]: NonNullable;
};

// 結果:
// {
// theme: “light” | “dark”;
// apiEndpoint: string;
// timeout: number;
// }

function initializeApp(config: AppConfig) {
// デフォルト値をパッチして完全なオブジェクトを作る例
const resolvedConfig: ValidatedConfig = {
theme: config.theme ?? ‘light’,
apiEndpoint: config.apiEndpoint ?? ‘https://api.example.com’,
timeout: config.timeout ?? 5000,
};

// resolvedConfig のプロパティは絶対に null/undefined にならない
console.log(resolvedConfig.theme.toUpperCase());
}

このイディオムは、大規模なアプリケーションの初期化ロジックや、複雑なRedux/Zustandのストアの初期化において、バグの温床を断ち切るために非常に有効だ。

—

4. シニアから後輩へ贈るベストプラクティスと注意点

最後に、現場で `NonNullable` を使う上での心構えをいくつか伝えておこう。

1. 「型汚染」の現実逃避に使わない
APIの設計がガバガバで、本来必須なはずのフィールドが平気で `null` で返ってくるような現場はある。その時に、面倒だからといって安易に `NonNullable` で型をねじ伏せるのは「現実逃避」だ。型エラーを消したところで、ブラウザ上でのランタイムエラー(Cannot read properties of null)は普通に爆発する。ランタイムのバリデーション(ZodやValibotなどのスキーマ検証ライブラリ)とセットで使うのがプロの仕事だ。

2. `undefined` との付き合い方
JavaScriptにおいて、存在しないプロパティアクセスは `undefined` になる。`NonNullable` は `null` も `undefined` もまとめて刈り取ってくれるため、厳密なドメインモデルを定義する際の「値の存在保証」としてはこれ以上ないほど強力だ。

—

まとめ

`NonNullable` は、一見すると地味なユーティリティ型に思えるかもしれない。しかし、その背後にある条件付き型の仕組みを理解し、実務のデータフローに組み込むことで、コードから無駄な防衛的コード(`if (!val)` の嵐)を駆逐することができる。

TypeScriptを書いている時間は、型エラーと戦う時間ではなく、「ビジネスロジックをいかに安全に、美しく表現するか」を楽しむ時間であるべきだ。

明日からのコードレビューで、無駄な `null` チェックの山を見つけたら、「ここに `NonNullable` 使えるんじゃないか?」と、ぜひ後輩にドヤ顔でアドバイスしてみてくれ。応援しているぞ!

コメント

タイトルとURLをコピーしました