【実務・中級編】 NonNullable – TypeScript実践ガイド

やあ、調子はどうだい?
最近、チームのコードレビューをしていて「またか」と頭を抱えたポイントがあるんだ。APIから返ってきたデータや、フォームの入力値を扱うときに、とりあえず `any` や型アサーション(`as`)で強引に型エラーを黙らせているコードを見かける。あれは、TypeScriptという強固な防壁に自ら穴を開けているようなものだ。

特に `null` や `undefined` のハンドリング。これに疲弊している中級エンジニアは本当に多い。
「この値、絶対に `null` じゃなくて `string` なはずなのに、型定義が `string | null` になってるからコンパイルエラーになる……くそっ、`as string` でいっちまえ!」――おいおい、ちょっと待て。その場しのぎの `as` は、数ヶ月後の自分やチームメンバーへの爆弾テロと同じだぞ。

今回は、そんな「Nullable地獄」から君をスマートに救い出し、実務の現場で明日から即座にドヤ顔で使える『NonNullable』の極意を伝授しよう。

—

1. `NonNullable` とは一体何者か?

TypeScriptの標準ライブラリ(`lib.es5.d.ts`)を覗いたことがあるかい?
もし見たことがなければ、今度 `Ctrl(Cmd) + クリック` で定義ジャンプしてみてほしい。`NonNullable` の実体は、拍子抜けするほどシンプルな数行のコードで書かれている。

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

たったこれだけだ。だが、この中にTypeScriptの条件付き型(Conditional Types)と `never` 型の魔術がギュッと詰まっている。

簡単に仕組みを解説しよう。
ジェネリクスに渡された型 `T` が、もし `null` または `undefined` に「割り当て可能(extends)」であるならば、それを `never`(=「存在しないこと」を表す底の型)に置き換える。そうでなければ、そのまま `T` を返す。

ユニオン型に対してこれを適用すると、分布条件付き型(Distributive Conditional Types)の働きによって、ユニオンの要素一つひとつに対してこの判定が走る。結果として、型から `null` と `undefined` だけが綺麗にパージされるというわけだ。

—

2. ブラウザの裏側とJavaScriptの現実

ここで少し視点を変えて、ブラウザの裏側の話をしよう。
TypeScriptはあくまで「開発時のトランスパイルと型チェック」のための道具だ。ブラウザ(JavaScriptエンジン)が実行するのは、型をすべて剥ぎ取られた素のJavaScriptである。

ブラウザのV8エンジンなどが動くとき、メモリ上にはプリミティブ値やオブジェクトが存在するが、そこに「TypeScriptの型情報」は1バイトも残っていない。つまり、ランタイム(実行時)においては、値が突然 `null` になったり `undefined` になったりするリスクは常に存在している。

APIの仕様書がどれほど「このフィールドは絶対に値を返します」と謳っていても、バックエンドのバグやネットワークの途絶、予期せぬJSONのパースミスによって、フロントエンドには容赦なく `null` が飛んでくる。

だからこそ、「ランタイムのガード(型ガード)」と「静的な型定義の絞り込み(NonNullable)」をセットで考える必要があるんだ。TypeScriptの `NonNullable` は、静的な世界で「ここから先は絶対に `null` や `undefined` を通さない」という強力な契約をコンパイラと結ぶためのツールなのだよ。

—

3. 現場で使える!実践コード例

百聞は一見にしかずだ。実務でよくあるユースケースをベースに、きれいなコードを見ていこう。

ユースケース1: 設定値やキャッシュの初期化と取得

アプリケーションのグローバルな設定や、ローカルストレージから取得したデータを扱うとき、最初は「まだ読み込まれていない」状態なので `null` や `undefined` を許容するが、初期化が完了した後は絶対に値が存在するケースだ。

// アプリケーションの設定情報の型
type AppConfig = {
theme: ‘light’ | ‘dark’;
apiEndpoint: string;
};

// 状態管理(Store)やコンテキストの型定義
// 初期化前は null であるため、このような型になりがち
type ConfigState = {
config: AppConfig | null | undefined;
isLoading: boolean;
};

// 【改善前】
// 使うたびに if (state.config !== null && state.config !== undefined) と書くか、
// 面倒くさくなって state.config!.theme と非nullアサーションを使うハメになる(最悪!)

// 【改善後: NonNullableを活用する】
// 初期化済みの確定した設定型を取り出す
type ActiveConfig = NonNullable;

function renderHeader(configState: ConfigState) {
// 型ガード(ガード関数やカスタムフックなど)で絞り込んだとする
if (!configState.config) {
console.log(‘設定の読み込み中…’);
return;
}

// このスコープ内では、TypeScriptが賢く推論してくれるが、
// 厳密に型を保証したヘルパー関数等に渡す場合は NonNullable が大いに役立つ
const currentConfig: ActiveConfig = configState.config;

console.log(`現在のテーマ: ${currentConfig.theme}`);
}

ユースケース2: フォームのバリデーション結果の抽出

複数の入力フィールドからなるオブジェクトがあり、それぞれのフィールドエラーメッセージ(または `null`)を保持している状態を考えてみよう。エラーが存在するフィールドの型だけを抽出したいとき、`NonNullable` が抜群のキレ味を発揮する。

type FormValues = {
username: string;
email: string;
age: number | null; // 未入力の場合は null
};

// 各フィールドのエラーメッセージを保持する型(エラーがない場合は null)
type FormErrors = {
[K in keyof FormValues]: string | null;
};

// ここで FormErrors から null を除外した「エラーメッセージの型」を作りたい!
type ActiveErrors = {
[K in keyof FormErrors]: NonNullable;
};
// 結果: { username: string; email: string; age: string; }
// ※厳密には string | null から null が消えて string になる

function displayErrorSummary(errors: FormErrors) {
// null(エラーなし)を除外して、エラーが存在するものだけを配列にする
// 型は (string | null)[] ではなく、キレイに string[] に推論される
const errorMessages = Object.values(errors).filter(
(err): err is NonNullable => err !== null
);

console.log(`現在 ${errorMessages.length} 件のエラーがあります。`);
}

この `filter` の書き方(ユーザー定義型ガード `err is NonNullable`)は、実務で死ぬほど使うから、ぜひ指に覚えさせておいてほしい。

—

4. シニアからのアドバイス:型安全の勘所

`NonNullable` は非常に強力だが、使いどころを間違えると「現実逃避のツール」になってしまう。

1. APIのレスポンスそのものに安易に使わない
バックエンドから来るデータが `null` を返す可能性があるのに、型定義の段階で `NonNullable` を貼ってフタをするのはご法度だ。「TypeScript上ではエラーにならないのに、本番環境でクラッシュした」という、一番最悪なパターンのバグを生む原因になる。APIの境界(Boundary)では、Zodなどのバリデーションライブラリを使って実行時検証を行い、その結果として「安全になったデータ」に対して使うのが正しいアプローチだ。

2. 非nullアサーション(`!`)の代わりに使う
「ここに絶対値があるのは分かっているんだ!」と言って `data!.id` のように感嘆符をつけるのは、TypeScriptの型システムに対する敗北宣言だ。コードの意図が曖昧になるし、将来の改修で `undefined` が入り込んだときにコンパイラが助けてくれなくなる。どうしても型を絞り込みたいときは、`NonNullable` を利用した型ガードやユーティリティ型を設計しよう。

—

まとめ

TypeScriptの基本型やユーティリティ型を使いこなせるようになると、コードの「意図」が圧倒的にクリアになる。
`NonNullable` は、その中でもシンプルながら、実務のコードベースを美しく保つためのいぶし銀の機能だ。

「とりあえず `as` で逃げる」のは今日で終わりだ。
型システムと対話し、コンパイラを味方につけた、泥臭くないスマートなフロントエンド開発を一緒に楽しもうぜ。さて、次のタスクに取り掛かるとしようか。

コメント

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