【実務・中級編】 真偽値による型ガード(Truthiness Narrowing) – TypeScript実践ガイド

お疲れ。最近、コードレビューをしていて「あ、これまだボヤッとした書き方してるな」って思うことが多いんだよね。特にAPIから返ってきた`null`や`undefined`が混じったデータをさばく時。

中級への階段を登っている君なら、`if (value)` って書けばTypeScriptがよしなに型を絞り込んでくれる(Truthiness Narrowing)、その便利さはもう体感してるはずだ。でもさ、「とりあえず動くから」でその裏のメカニズムをスルーしてると、ある日突然、空文字や数字の `0` に足をすくわれてバグを踏み抜くことになる。

今回は、この真偽値による型ガード(Truthiness Narrowing)について、ブラウザのランタイムの動きから実務で絶対に知っておくべきベストプラクティスまで、徹底的に解説しようか。

—

1. Truthiness Narrowing(真偽値絞り込み)の正体

まず基本のおさらいだ。TypeScriptは賢いから、`if (val)` という条件分岐を書くだけで、そのブロックの中では `val` から「Falsyな値」をごっそり削ぎ落としてくれる。

function processInput(input: string | null | undefined) {
// ここで input は string | null | undefined
if (input) {
// このブロックの中では、TypeScriptは input を string と確信する
console.log(input.toUpperCase());
}
}

この挙動自体は非常に直感的で最高なんだけど、ここで一歩立ち止まって考えてほしい。「Falsyな値」って具体的に何だっけ?

JavaScriptのランタイム(V8などのJSエンジン)において、以下の6つの値は「Falsy(偽と評価される値)」として扱われる。
1. `false`
2. `0`(数値のゼロ)
3. `””`(空文字)
4. `null`
5. `undefined`
6. `NaN`

つまり、`if (input)` と書いた瞬間、TypeScriptは `null` や `undefined` だけでなく、空文字 `””` や数値の `0` までも排除してしまうんだ。これが実務でめちゃくちゃ厄介なバグを生む温床になる。

—

2. 現場でよくある「やっちまった」事例

フロントエンドでフォームの入力値を扱う場面を想像してほしい。ユーザーが数値を入力するフィールドがあって、その値は `number | null` だとする。

function calculateDiscount(price: number | null) {
// 「0円なら割引なし、nullなら未設定」と判定したい意図とする
if (!price) {
return “価格が無効か未設定です”;
}

return price 0.9;
}

このコード、何が問題かわかるかい?
ユーザーが「0円」という有効なデータを入力(あるいはAPIから取得)したとき、JavaScriptのランタイムでは `0` は Falsy なので `!price` は `true` になり、「価格が無効か未設定です」と判定されてしまうんだ。

「えっ、0円も弾かれちゃうの!?」っていうバグ、君のプロジェクトでも見覚えがないか? これが、真偽値による絞り込みを雑に使ったときの典型的な敗因だ。

—

3. 実務で使える!安全な型絞り込みのベストプラクティス

じゃあ、我々プロのフロントエンドエンジニアはどう書くべきか。
結論から言うと、「何を排除したいのか」によって絞り込みの方法を完全に使い分けるべきだ。

パターンA: 単に `null` と `undefined` だけを排除したい場合

文字列やオブジェクトなどで、存在チェック(`null` / `undefined` の排除)だけが目的なら、Truthiness Narrowingを使ってもいい。ただし、空文字を有効な値として扱いたい場合は注意が必要だ。

一番安全なのは、比較演算子を明示して、型ガードの範囲を自分でコントロールすることだ。

type UserProfile = {
bio: string | null;
};

function renderBio(profile: UserProfile) {
// null と undefined のみを明確に弾く(空文字 “” は有効な自己紹介文として通す場合)
if (profile.bio !== null && profile.bio !== undefined) {
// ここで profile.bio は string 型に絞り込まれる(”” も許容される)
console.log(`自己紹介: ${profile.bio.length}文字`);
}
}

パターンB: 数値や文字列で `0` や `””` を正しく扱いたい場合

前述の `price` の例のように、`0` や `””` が「意味のある値(Truthyなデータ)」として扱われるべき場合は、`undefined` や `null` との厳密な比較を行うのがセオリーだ。

function calculateDiscountSafe(price: number | null | undefined) {
// null でも undefined でも無いことを厳密にチェックする
// これにより、価格 “0” は正しく処理される
if (price === null || price === undefined) {
return “価格が設定されていません”;
}

// ここに到達した時点で price は確実に number 型(0も含む)
return price 0.9;
}

—

4. チームメンバーに伝えたい、シニアからのアドバイス

TypeScriptの型システムは、JavaScriptのランタイムの挙動(Truthy / Falsy)の上に成り立っている。だからこそ、「TypeScriptがどう型を解釈しているか」と「JavaScriptがどう値を評価しているか」のギャップに常に気を配る必要があるんだ。

1. `if (value)` は、「存在チェック+文字列や数値のデフォルト値(0や””)の排除」を同時に行う諸刃の剣だと心得よ。
2. APIスキーマの型定義(特にバックエンドから来る緩いデータ)を扱うときは、極力 `=== null` や `!== undefined` で明示的にガードを書く癖をつけよう。
3. カスタム型ガード関数(User-Defined Type Guards)を作って、ドメインロジックに合わせた堅牢な絞り込みを提供するのも、アーキテクチャを美しく保つための上級テクニックだ。

// 応用:カスタム型ガードで意図を明確にする
function isNonEmptyString(value: unknown): value is string {
return typeof value === “string” && value.trim().length > 0;
}

function processUsername(name: unknown) {
if (isNonEmptyString(name)) {
// このブロック内では name は確実に string として安全に扱える
console.log(name.toUpperCase());
}
}

コードを書くときは、「動けばいいや」じゃなくて、「1年後のチームメンバーが読んでも、意図が秒で伝わるか」を意識しよう。こういう細かい型のコントロールの積み重ねが、プロダクトの寿命を大きく伸ばすんだからさ。

それじゃ、今日のレビューはこの辺で。また何か引っかかったらいつでも相談してくれ。

コメント

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