やあ、調子はどうだい?
最近、チームのコードレビューをしていて「またか」と頭を抱えたポイントがあるんだ。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
たったこれだけだ。だが、この中に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
—
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
);
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` で逃げる」のは今日で終わりだ。
型システムと対話し、コンパイラを味方につけた、泥臭くないスマートなフロントエンド開発を一緒に楽しもうぜ。さて、次のタスクに取り掛かるとしようか。

コメント