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

こんにちは。チームを支えるシニアフロントエンドエンジニアの私だ。

君も日々の開発で、APIから返ってきたレスポンスや、フォームの入力値の型定義に頭を悩ませていることだろう。「本当はここ、絶対に`null`も`undefined`も入らないはずなのに、TypeScriptの型推論が優しすぎて(あるいは厳しすぎて)`string | null | undefined`になっちまう……!」なんて叫び声をあげた夜が、一度や二度ではないはずだ。

型安全を追求するあまり、コードのあちこちに無駄なオプショナルチェイニングやガード構文(`if (val != null)`)を書き散らして、ロジックが見づらくなっていませんか?

今回は、そんな日々のフラストレーションを鮮やかに解消してくれる、TypeScript標準のユーティリティ型 `NonNullable` について、実務でどう使い倒すべきか、裏側の仕組みから現場のベストプラクティスまでみっちり解説しよう。

—

1. そもそも `NonNullable` とは何か?(基本のおさらい)

公式ドキュメントを開けば、「型 `T` から `null` と `undefined` を除外する」とサラッと書いてある。だが、その本質をコードレベルで正確に理解している人は意外と少ない。

まずは定義を見てみよう。TypeScriptの内部(`lib.es5.d.ts`)では、こいつは以下のように極めてシンプルに実装されている。

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

おっと、ここで条件付き型(Conditional Types)の登場だ。
「もし `T` が `null` または `undefined` に割り当て可能(extends)なら `never` にし、そうでなければそのまま `T` を返す」という非常にシンプルな仕組みになっている。

`never` 型というのは、いわば「存在し得ない値」を示す底の型だ。Union型の中に `never` が混ざると、TypeScriptの型システムはそれを綺麗に消去してくれる(Distributive Conditional Typesの恩恵だ)。

例えば、次のような型があったとしよう。

type MaybeString = string | null | undefined;

// NonNullableを適用すると…
type DefiniteString = NonNullable; // 結果: string

これによって、`null` や `undefined` という「不確実なノイズ」をきれいにパージし、「値が絶対に存在すること」を型レベルで保証できるようになるわけだ。

—

2. ブラウザの裏側とTypeScriptのランタイムの現実

さて、ここで少し視点を変えて、ブラウザやJavaScriptのランタイムの裏側の話をしよう。

TypeScriptは、あくまでの「開発時の静的解析ツール」だ。コンパイル(トランスパイル)されて生成されるJavaScriptのコードには、型情報は1バイトたりとも残らない。
つまり、`NonNullable` を使ったからといって、ブラウザのJavaScriptエンジンが自動的に `null` チェックのコードを生成してくれたり、実行速度が速くなったりするわけではないのだ。

じゃあ何の意味があるのか?
それは、「開発者の脳内にある前提条件を、TypeScriptのコンパイラと完全に共有するため」にある。

例えば、バックエンドから送られてくるJSONデータには、たまに設計書に書いていない `null` が混ざってきたりする。ブラウザは `TypeError: Cannot read properties of null` を吐いて盛大にクラッシュする。
ここで `NonNullable` を使うのは、「俺はこの変数が `null` や `undefined` じゃないと信じている(あるいは、そうであるべきデータを扱う)」という強い意志表示であり、もし万が一ランタイムで `null` が流れてきたときに、コンパイルエラーや早期の型ガードで気付けるようにするための防壁なのだ。

—

3. 【実践】現場ですぐに使えるユースケースとサンプルコード

理屈はこれくらいにして、実際のフロントエンド開発でどう使うのか、コピペしてそのまま読める実用的なサンプルコードを見ていこう。

ユースケース A: APIレスポンスの配列から「空っぽの要素」を根絶する

これが実務で一番多いパターンだ。APIから取得したユーザーリストの中に、退会済みなどで `null` が混ざっているケースを考えてみよう。

// サーバーから返ってくる生データの型
interface User {
id: string;
name: string;
}

type RawUserResponse = (User | null | undefined)[];

// 🔴 従来の泥臭い書き方
// 配列を回すたびに `user?.name` と書く必要があり、冗長になる
const rawUsers: RawUserResponse = [
{ id: “1”, name: “Alice” },
null,
{ id: “2”, name: “Bob” },
undefined,
];

// 🟢 NonNullableを活用したスマートなアプローチ
// filterの型ガード(User is User)と組み合わせるのが実務の定石だが、
// 単に確実な値だけを抽出したい場合は NonNullable が火を吹く
const cleanUsers: NonNullable[] = rawUsers.filter(
(user): user is NonNullable => user != null
);

// これで cleanUsers の型は User[] になり、以降の処理で余計なオプショナルチェイニングが不要になる!
cleanUsers.forEach((user) => {
console.log(user.name.toUpperCase()); // 安全にアクセス可能!
});

ユースケース B: フォームのバリデーション結果の型安全なマッピング

ReactやVueで複雑なフォームを扱う際、各フィールドの状態を管理するオブジェクトを考えてみよう。ユーザーがまだ入力していない状態は `null` や `undefined` だが、確認画面や送信フェーズでは「すべて値が埋まっていること」が前提になる。

// フォームの入力値の型(最初は空なので nullable)
interface FormValues {
username: string | null;
email: string | null;
age: number | undefined;
}

// 送信データ用の型を NonNullable で一括生成する
// Mapped Types と組み合わせることで、元のインターフェースを維持したまま中身だけを厳格化できる
type ValidatedFormValues = {
[K in keyof T]: NonNullable;
};

type FinalPayload = ValidatedFormValues;
/
Generated Type:
{
username: string;
email: string;
age: number;
}
/

// 実務での送信処理関数
function submitForm(data: FinalPayload) {
// ここに到達した時点で、値が欠損していないことが型レベルで100%保証されている
console.log(`Submitting to: ${data.email}, User: ${data.username}`);
}

// 実際の使い方
const currentForm: FormValues = {
username: “Taro Dev”,
email: “taro@example.com”,
age: 28,
};

// バリデーションを通過したと仮定してキャスト、あるいはアサーションを行う場合
if (currentForm.username && currentForm.email && currentForm.age !== undefined) {
// ここで自動的に型が絞り込まれるが、ユーティリティ型として強制したい場合にもNonNullableは有用
submitForm(currentForm as FinalPayload);
}

—

4. シニアから後輩へ贈る、実務でのアンチパターンと注意点

最後に、現場でやりがちな「危うい使い方」について釘を刺しておこう。

1. ランタイムの現実逃避に使わない
`NonNullable` は魔法の杖ではない。サーバーから本当に `null` が返ってくる可能性があるにもかかわらず、型を合わせたいがために `as NonNullable` のように強引な型アサーション(Type Assertion)で型エラーをねじ伏せるのは最悪のアンチパターンだ。Runtime Errorが爆発する未来しか見えない。
型は「現実」に合わせるものであり、現実を型にねじ曲げてはいけない。

2. Zodなどのバリデーションライブラリとの使い分け
現代のフロントエンド開発では、外部入力をランタイムで安全に検証するために `Zod` や `Valibot` などのスキーマバリデーションを使うことが多い。UIコンポーネント内の厳密な型変換には `NonNullable` が便利だが、API境界(I/O境界)ではきちんとバリデーションライブラリを使ってランタイムの安全性を担保した上で、`z.infer` を使おう。

—

まとめ

  • `NonNullable` は、型 `T` から `null` と `undefined` を排除し、「確実に値が存在する状態」を作るための強力なユーティリティ型。
  • コンパイル時の静的解析をリッチにするものであり、ランタイムの安全性を担保するためには適切なガードやバリデーションと併用する必要がある。
  • 配列のフィルタリングや、フォームデータの確定フェーズなど、「ここから先は絶対に値がある」と断言できるシーンで使うと、コードの可読性が劇的に向上する。

型定義を制する者は、フロントエンドの保守性を制す。
ぜひ今日のコードから `NonNullable` を適材適所で取り入れて、無駄な `if` 文や `?.` の山から抜け出してほしい。健闘を祈る!

コメント

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