🙄

【JS】分割代入の初期値はFalsyでもNullishでもなくundefinedの時だけ働く

2021/12/14に公開

概要

タイトルが全てを物語っていますが、自分の中で思い違いをしていた(+知らないといつかやらかしそう)ので紹介します。
Reactの記事でよく使われる、JSの分割代入の話です。

経緯

とあるアプリケーションのバックエンドをNodeで実装していて、対象データのUPDATE処理を書いていた時でした。
引数はREST APIPOSTで受け取ったリクエストボディがそのまま入ってくるイメージだったので、ざっくりとコードは以下のような感じです。

キーにするidと必須項目であるpropAは入力チェックをして、任意のpropBは値があれば更新し、なければしないというものです。
propBについては フロント側から空文字で送られてきた場合も更新したくない ため、分割代入した時にデフォルト値をnullにすることで対応する意図でした。

const updateHoge = (body) => {
    const { id, propA, propB = null} = body;

    if(id && propA) {
        let sql = [` UPDATE HOGE SET`];
        sql.push(` PROP_A = ${propA} `);
        if(propB !== null) {
            sql.push(` PROP_B = ${propB} `);
        }
        sql.push(` WHERE id = ${id} `);
    }
    else {
        // id,propAが不正な場合のエラー処理
    }
}

実際sql周りの処理はもうちょっと綺麗に書いていましたが、だいたいは上記のようなイメージです。
察しのいい方ならもうお分かりかと思いますが、この書き方だと propBが空文字でも空文字で更新してしまいます

分割代入の初期値(規定値)はundefinedの時のみはたらく

ReactのコンポーネントでPropsを変数に格納する時などに以下の書き方をされている方も多いかと思います。

const {hoge, fuga = 10} = props;

この時にfuga=10と記載しているのが分割代入の初期値(規定値)で、自分はこれがFalsyの時に動作するものだとばかり思っていました。

Falsyとは文字通りfalseとして判定される値のことで空文字の""NaNなどがそれにあたります。
過去にも自分が以下のような記事でまとめているので、興味がある方はそちらを参照ください。

https://zenn.dev/nekoniki/articles/852199ebe23745abb1e7

Falsyの他にNullishという値もあり、これはnullもしくはundefinedを指します。
Null合体演算子などを使われている方にはおなじみの言葉だと思います。

// fugaがNullishの場合のみ??の後の文字列が入る
const hoge = fuga ?? "fuga is Nullish";

さて話を分割代入に戻しますが、 分割代入の初期値が使われるのは対象のプロパティがFalsyの時やNullishの時ではなくundefinedの時 です。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

オブジェクトから取り出した値が undefined であるときの既定値を、変数に割り当てることができます。

冒頭のコードは以下のようになっていました。

const { id, propA, propB = null} = body;

これはpropBundefinedの時のみnullが入り、それ以外のケースではpropBの値を使います。
従ってpropBが空文字""の場合はnullに置き換わらずにそのまま動作します。
この場合どうすればよかったかというと、後述のSQLの処理中でpropBが空文字かどうかを判定してあげればいいと思います。
※もしくはFalsyかどうかで判定するのもよいかと思います。

一応試してみる

簡単に試せそうなので、検証してみました。

const test = {
    a: null,
    b: "",
    c: 0,
    d: 1,
    e: undefined,
    f: {},
    g: [],
    h: NaN,
    i: false,
    j: true,
}

const {
    a = "CHANGED",
    b = "CHANGED",
    c = "CHANGED",
    d = "CHANGED",
    e = "CHANGED",
    f = "CHANGED",
    g = "CHANGED",
    h = "CHANGED",
    i = "CHANGED",
    j = "CHANGED",
    k = "CHANGED"
} = test;

console.log(" a --> " + a);
console.log(" b --> " + b);
console.log(" c --> " + c);
console.log(" d --> " + d);
console.log(" e --> " + e);
console.log(" f --> " + f);
console.log(" g --> " + g);
console.log(" h --> " + h);
console.log(" i --> " + i);
console.log(" j --> " + j);
console.log(" k --> " + k);

これを実行すると以下のようになります。

 a --> null
 b --> 
 c --> 0
 d --> 1
 e --> CHANGED
 f --> [object Object]
 g --> 
 h --> NaN
 i --> false
 j --> true
 k --> CHANGED

確かに明示的にundefinedにしたeと、そもそも定義しなかった(暗黙的にundefinedにした)kだけが"CHANGED"の値に置き換わっています。

まとめ

今回はJavascriptの分割代入において、初期値が入る条件について自分が思い違いをしていたので紹介しました。
FalsyNullishといった値の判定をミスると、回り回って大きな障害につながる可能性もあるので、忘れずに抑えていきたいところです。

今回の内容が役立てば幸いです。

Discussion