JavaScript中級 #5 オプショナルチェーンと nullish 合体

#4 デストラクチャリング/spread 詳細に続いて、この記事は ES2020 で追加された短いが強力な二つの演算子 — オプショナルチェーン(?.)nullish 合体(??) を整理します。

?. — オプショナルチェーン #

深いオブジェクトに安全にアクセスする演算子。

昔の方式 vs オプショナルチェーン
// 昔
const street = user && user.address && user.address.street;

// モダン
const street = user?.address?.street;

同じ仕事を半分の長さで。usernull/undefined ならそこで止まり undefined を返します。その後は進みません。

動作ルール #

?. の意味は正確に一行で:

左の値が null または undefined ならそこで止まり undefined 返却。それ以外は普通に評価。

ルール確認
const obj = null;

obj?.foo;           // undefined (null なので止まる)
obj?.foo.bar;       // undefined (止まったので .bar は評価されない)

const obj2 = { foo: { bar: 42 } };
obj2?.foo?.bar;     // 42 (どれも意味のある値)
obj2?.unknown?.x;   // undefined

途中で一度でも null/undefined があればそこで stop、結果は undefined

関数呼び出し — ?.() #

存在するか分からないメソッド/コールバックを安全に呼び出す。

関数があるときだけ
const callback = options.onSuccess;
callback?.();

obj.greet?.();

callback が定義されていれば呼び出し、なければ何も起こりません。コールバックオプションを受け取る関数でよく使うパターンです。

配列/角括弧 — ?.[] #

配列の安全アクセス
const items = config?.items;
const first = config?.items?.[0];
const dynamic = obj?.[someKey];

?. に角括弧を直接つけることはできず — ?.[] または ?.[key] で書きます。

?. の落とし穴 — すべての場面に使わないこと #

オプショナルチェーンが便利だからとあらゆる場面に差し込むとバグが隠れて入り込みます

過剰な ?. の落とし穴
function processUser(user) {
  return user?.id?.toUpperCase();   // ✗ 過剰に防御的
}

user が本当にオプショナルなら OK。しかし関数の引数として常に user が入ってくるべきなのが正しいなら、?.バグを静かに飲み込みますusernull で入ってきたとき、それが本当の問題かもしれません。

良いガイド: 本当にない可能性のある場面にだけ ?. を使います。「念のため」式にあらゆる場面に差し込まないでください。

?? — Nullish 合体 #

null または undefined のときに既定値を与える演算子。

?? 基本
const name = user.name ?? '匿名';
const age = user.age ?? 0;

左の値が null/undefined なら右、そうでなければ左そのまま。

?? vs || — 違いは falsy と nullish #

昔は || で同じ仕事をしていました。違いは微妙ですが重要です。

|| と ?? の比較
const a = 0 || 10;       // 10  (0 は falsy)
const b = 0 ?? 10;       // 0   (0 は nullish ではない)

const c = '' || 'default';   // 'default'
const d = '' ?? 'default';   // ''

const e = false || true;     // true
const f = false ?? true;     // false

|| は falsy 7種(false/0/-0/0n/’’/null/undefined/NaN)に対してすべて発動します。?? は正確に null/undefined の二つだけに発動します。

これがなぜ重要かというと — 0''false のような意味のある値が falsy であることが、予期しないバグを引き起こしていました。

昔の || の落とし穴
function withTimeout(timeout) {
  const ms = timeout || 5000;   // ✗ timeout=0 まで弾いてしまう
  // ...
}

withTimeout(0);   // ms = 5000, 0 が無視される
?? が正解
function withTimeout(timeout) {
  const ms = timeout ?? 5000;
  // ...
}

withTimeout(0);   // ms = 0

既定値パターンはほぼ常に ?? が意図と合います。|| は「空であるか0なら既定値」が本当に意図のときだけ使います。

二つが出会うと — ?. + ?? #

よく組まれます。

オプショナル + 既定値
const username = response?.user?.name ?? '匿名';
//                        |               |
//                        |               空のときに既定値
//                        深い安全アクセス

const port = config?.server?.port ?? 3000;

深いところに安全にアクセスしながら空なら既定値 — 一行で解決します。モダンJavaScriptの最も多い慣用句の一つです。

??= と仲間たち — 論理代入演算子 #

ES2021 で追加された短い形。

??= — null/undefined のときだけ代入
let user = { name: 'カーティス' };

user.email ??= 'default@example.com';
// user.email が null/undefined なら代入、そうでなければそのまま

三つのバリエーションがあります。

三つの論理代入
a ||= b;    // a が falsy なら a = b
a ??= b;    // a が nullish なら a = b
a &&= b;    // a が truthy なら a = b

||=??= の違いは上で見たのと同じです — falsy vs nullish。

落とし穴 — 演算子の優先順位 #

??||/&& を混ぜるときは括弧が必須です。JavaScriptがコンパイル段階で防いでくれます。

混合は括弧で
const a = null ?? true && false;     // ✗ SyntaxError
const b = (null ?? true) && false;   // OK — false
const c = null ?? (true && false);   // OK — false

言語レベルで意図が曖昧な表現を防いだのです。間違いを減らしてくれます。

オプショナルチェーンとデストラクチャリング #

オプショナル + デストラクチャリング
const { user: { name } = {} } = response ?? {};

複雑そうに見えますが二段階だけ解けばよいです。

  1. response ?? {} — response が null/undefined なら空オブジェクトで開始
  2. user: { name } = {} — user がなければ空オブジェクトから開始 (そうすれば name = undefined)

このように書くのがあまりにきついなら、いっそ広げて書くのが良いです。

広げて書く — 時々より明確
const user = response?.user;
const name = user?.name ?? '匿名';

オプショナルチェーンと単純チェック — どちらが良いか #

単純チェック vs チェーン
// オプショナルチェーン
if (user?.address?.street) {
  // ...
}

// 明示的なチェック
if (user && user.address) {
  // ...
}

ほとんどの場面で ?. が短く直感的です。ただしチェック自体が意図なら明示的なチェックのほうが意図を表しています。ほぼ慣習の違いなのでチームスタイルに従ってください。

まとめ #

この記事で整理した内容:

  • ?. — 左が null/undefined なら止まり undefined 返却
  • ?.()?.[] の形で関数呼び出し / 配列アクセスにも使用
  • すべての場面に ?. を差し込まないこと — 本当にオプショナルな場面にだけ
  • ?? — null/undefined のときだけ発動
  • ?? vs || — 0、’’、false のような意味のある値を生かすかの違い
  • 既定値パターンはほぼ常に ?? が意図と合う
  • ??=||=&&= — 論理代入演算子
  • ??||/&& と混ぜるときは括弧必須

次の記事(#6 fetch API とエラー処理)では、ブラウザ/Node 両方で動作する標準ネットワークツール — fetch の使い方とエラー処理、AbortController まで扱います。

X