本文へスキップ
truthy / falsy と && || ??

Part 0 · React の前に

6 / 64

truthy / falsy と && || ??

条件によって出し分けるための土台

React で「件数が 0 のときは何も出さない」と書いたはずなのに、 画面に 0 だけがぽつんと表示される。 これは初心者が必ず一度は遭遇するバグです。

原因は && という記号の性質にあります。これは「かつ」ではなく、値を返す演算子だからです。

この章では、そのからくりを先に潰しておきます。

JavaScript は何でも真偽に変換する

if の中に true / false 以外を書いても、JavaScript は文句を言いません。 勝手に「真っぽいか、偽っぽいか」に変換して判断します。

このとき偽として扱われる値は 6 つだけです。 これ以外は全部、真として扱われます。

偽になる値と、間違えやすい値

空の配列も空のオブジェクトも「真」になる

偽として扱われる(これで全部)

  • false
  • 0
  • ""
  • null
  • undefined
  • NaN

真として扱われる(間違えやすいもの)

  • []
  • {}
  • "0"
  • "false"
  • -1
  • " "

falsy.ts

// 偽として扱われる値は、この 6 つだけ
export const falsyValues = [false, 0, "", null, undefined, NaN];

// それ以外は全部「真」として扱われる。
// 中身が空でも、文字が "0" や "false" でも真になる
export const truthyButSurprising = [[], {}, "0", "false", -1, " "];

&& と || は「値を返す」

演算子とは、+ && のような記号のことです)

ここが本題です。&&|| は、true か false を返すのではありません。 左右どちらかの値そのものを返します。

  • A && B … A が偽なら A を返す。真なら B を返す
  • A || B … A が真なら A を返す。偽なら B を返す
  • A ?? B … A が nullundefined のときだけ B を返す
同じ形でも、返るものが違う

0 のときの結果に注目

  • count && "あり"0
  • stock && "あり""あり"
  • keyword || "デフォルト""デフォルト"
  • count || "デフォルト""デフォルト"
  • count ?? "デフォルト"0
  • nickname ?? "デフォルト""デフォルト"

count が 0 のとき、count && "あり" の結果は false ではなく 0 です。 左が偽だったので、そのまま左の値が返っています。

operators.ts

/*
  && は 左が偽ならその左の値を返し、真なら右の値を返す
  || は 左が真ならその左の値を返し、偽なら右の値を返す
  ?? は 左が null か undefined のときだけ右の値を返す
*/

// 実際のデータと同じように、変数に入った値で試す
const count: number = 0;
const stock: number = 5;
const keyword: string = "";
const nickname: string | null = null;

export const examples = [
  { code: 'count && "あり"', result: count && "あり" },
  { code: 'stock && "あり"', result: stock && "あり" },
  { code: 'keyword || "デフォルト"', result: keyword || "デフォルト" },
  { code: 'count || "デフォルト"', result: count || "デフォルト" },
  { code: 'count ?? "デフォルト"', result: count ?? "デフォルト" },
  { code: 'nickname ?? "デフォルト"', result: nickname ?? "デフォルト" },
];

これが「画面に 0 が出る」の正体

React では、条件によって表示を出し分けるときにこう書きます。

{items.length && <p>{items.length} 件あります</p>}

件数が 3 なら、3 && <p>...</p> で右側が返り、 文章が表示されます。ここまでは意図どおりです。

ところが件数が 0 のとき、この式は 0 を返します。そして React は、数値の 0 を画面に表示します。 非表示にしたかったのに、0 という文字が残るのはこのためです。

// ✕ 0 が画面に出る
{items.length && <p>{items.length} 件あります</p>}

// ○ 比較して true / false にしてから渡す
{items.length > 0 && <p>{items.length} 件あります</p>}
0 が出るところを見る

「1 つ減らす」を押して、件数を 0 まで減らしてみる

items.length は 3

&& にそのまま渡した(0 が出る)
3 件あります
比べてから渡した(何も出ない)
3 件あります

件数が 1 以上のあいだは、2 つの箱は同じに見えます。0 にした瞬間だけ、上の箱に0 という文字が残ります。 この章の最初に書いた「画面に 0 だけがぽつんと表示される」は、 これのことです。

false は React が画面に出さないと決めている値なので、 何も表示されません。

operators.ts

/*
  && は 左が偽ならその左の値を返し、真なら右の値を返す
  || は 左が真ならその左の値を返し、偽なら右の値を返す
  ?? は 左が null か undefined のときだけ右の値を返す
*/

// 実際のデータと同じように、変数に入った値で試す
const count: number = 0;
const stock: number = 5;
const keyword: string = "";
const nickname: string | null = null;

export const examples = [
  { code: 'count && "あり"', result: count && "あり" },
  { code: 'stock && "あり"', result: stock && "あり" },
  { code: 'keyword || "デフォルト"', result: keyword || "デフォルト" },
  { code: 'count || "デフォルト"', result: count || "デフォルト" },
  { code: 'count ?? "デフォルト"', result: count ?? "デフォルト" },
  { code: 'nickname ?? "デフォルト"', result: nickname ?? "デフォルト" },
];

右側は、実行されないことがある

&& は、左が偽なら、右をそもそも実行しません。 左だけ見て答えが決まってしまうからです。

user && user.getName()   // user が null なら、getName() は呼ばれない

これは安全装置としてよく使われます。 左が空のときに右で落ちるのを、これ 1 つで防げます。

3 つに分かれる書き方(三項演算子)

&& は「あるときだけ出す」でした。「A か B のどちらかを出す」ときは、別の書き方を使います。

条件 ? 条件が真のとき : 条件が偽のとき

isLoggedIn ? "さとうさん" : "ゲスト"

?: の 3 つの部分に分かれるので三項演算子と呼びます。if と違って値になるので、 このあと JSX の中でよく使うことになります。

|| と ?? の使い分け

初期値を用意したいとき、つい || を使ってしまいますが、 これも 0 で事故が起きます。

const count = 0;

count || 10; // 10  ← 0 は偽なので置き換えられてしまう
count ?? 10; // 0   ← null / undefined でないのでそのまま

「値が入っていないときだけ初期値を使いたい」のであれば、?? を使うのが正解です。|| は 0 や空文字まで「なかったこと」にしてしまいます。

operators.ts

/*
  && は 左が偽ならその左の値を返し、真なら右の値を返す
  || は 左が真ならその左の値を返し、偽なら右の値を返す
  ?? は 左が null か undefined のときだけ右の値を返す
*/

// 実際のデータと同じように、変数に入った値で試す
const count: number = 0;
const stock: number = 5;
const keyword: string = "";
const nickname: string | null = null;

export const examples = [
  { code: 'count && "あり"', result: count && "あり" },
  { code: 'stock && "あり"', result: stock && "あり" },
  { code: 'keyword || "デフォルト"', result: keyword || "デフォルト" },
  { code: 'count || "デフォルト"', result: count || "デフォルト" },
  { code: 'count ?? "デフォルト"', result: count ?? "デフォルト" },
  { code: 'nickname ?? "デフォルト"', result: nickname ?? "デフォルト" },
];

そもそも「無いものの中」を見に行かない

??値が無かったときの話でした。 もう 1 つ、そこへ辿り着く途中が無いという場合があります。

const user = { name: "すずき" };  // address が無い

user.address.city

これは undefined の中を見に行こうとして、その場で落ちます

無い項目の中を取り出してみるうまくいかない例

ボタンを押すと、その箱の中だけが壊れます

?. を付けない(address が無い人)

まだ実行していません

?. を付けた

address?.city は undefinedaddress?.city ?? "未登録" は 未登録

Cannot read properties of undefined という エラーが出ます。初学者がいちばん多く出会うエラーで、 原因はたいてい「途中が無かった」です。

途中が無ければ、そこで止める

user.address?.city         // undefined(落ちない)
user.address?.city ?? "未登録"  // "未登録"

?. を挟むと、手前が nullundefined なら、 そこで止まって undefined を返します。 その先を見に行かないので落ちません。

?? と組み合わせるのが定番です。「無ければ止める」と「無かったときの値」を つなげて書けます。

optional-chain.ts

type User = {
  name: string;
  address?: { city: string };
};

const withAddress: User = { name: "さとう", address: { city: "東京" } };
const withoutAddress: User = { name: "すずき" };

/*
  address がある人でも、そのまま .city とは書けない。
  型の上では「無いかもしれない」ままなので、TypeScript が止めてくれる。
  (型を付けていない JavaScript では止めてくれない。落ちてから気づく)
*/
export const city1 = withAddress.address?.city; // "東京"

/*
  ?. を外すとどうなるか。

  型を付けていれば、TypeScript が書いた時点で止めてくれる
  (下の行のコメントを外すと、赤線が出る)。
*/
// export const city2 = withoutAddress.address.city;

/*
  では型が無ければどうなるか。実行して、その場で落ちる。

    Cannot read properties of undefined (reading 'city')

  undefined の中を見に行こうとしたため。
  型を付けない JavaScript では、動かすまで気づけない。
*/

// ?. を挟むと「途中が無ければ、そこで undefined を返す」になる
export const city3 = withoutAddress.address?.city; // undefined

// ?? と組み合わせると、無いときの表示まで決められる
export const city4 = withoutAddress.address?.city ?? "未登録"; // "未登録"

// 関数にも使える。「あれば呼ぶ」
type Item = { name: string; onSelect?: () => void };
const item: Item = { name: "りんご" };
export const called = item.onSelect?.(); // undefined(呼ばれない)

理解できたか確かめる

確認クイズ

0 && '表示' の結果は?

確認クイズ

件数が 0 のときは何も表示したくない。正しい書き方は?

確認クイズ

const count = 0; のとき、count ?? 10 の結果は?

確認クイズ

user.address が無いとき、user.address.city はどうなる?

operators.ts

/*
  && は 左が偽ならその左の値を返し、真なら右の値を返す
  || は 左が真ならその左の値を返し、偽なら右の値を返す
  ?? は 左が null か undefined のときだけ右の値を返す
*/

// 実際のデータと同じように、変数に入った値で試す
const count: number = 0;
const stock: number = 5;
const keyword: string = "";
const nickname: string | null = null;

export const examples = [
  { code: 'count && "あり"', result: count && "あり" },
  { code: 'stock && "あり"', result: stock && "あり" },
  { code: 'keyword || "デフォルト"', result: keyword || "デフォルト" },
  { code: 'count || "デフォルト"', result: count || "デフォルト" },
  { code: 'count ?? "デフォルト"', result: count ?? "デフォルト" },
  { code: 'nickname ?? "デフォルト"', result: nickname ?? "デフォルト" },
];

この章のまとめ

  • 偽として扱われるのは false, 0, "", null, undefined, NaN の 6 つだけ。空の配列やオブジェクトは
  • &&|| は true / false ではなく値そのものを返す
  • {count && <p>…</p>} は、count が 0 のとき画面に 0 を出す。count > 0 && と書けば防げる
  • 初期値を入れたいときは || ではなく ??

falsy.ts

// 偽として扱われる値は、この 6 つだけ
export const falsyValues = [false, 0, "", null, undefined, NaN];

// それ以外は全部「真」として扱われる。
// 中身が空でも、文字が "0" や "false" でも真になる
export const truthyButSurprising = [[], {}, "0", "false", -1, " "];