ネストされたオブジェクトのimmutableな更新方法
react reduxの設計をしていると、stateの状態はimmutableであるべきで、reducerの処理で新規stateオブジェクトを生成する際にstateを直接更新してはならないという制約に出くわす。このルールを怠ると、stateの更新履歴が正しく管理されなくなり、Undo/RedoなどReduxが得意とする処理が壊れるなどの副作用をきたす。
Immutableは分かりにくいが、英訳すると「不変」を意味し、Wikipediaから情報を借りるとimmutableなobjectは次のように記述されている。
イミュータブル なオブジェクトとは、作成後にその状態を変えることのできないオブジェクトのことである。
Reduxで言うと、状態変更前のオブジェクトに変更を加えるべからずという意味となる。前置きは長くなったが、ネストが深くなった時に、前状態を踏襲して新規状態オブジェクトを作成するのは少しテクニックが必要となるため例を示す。
const prevState = {
a: {
b: {
c: {
d: 1
}
e: 2
}
f: {
g: 3
}
}
}
というオブジェクトがあり、これを新規状態ではeを3にしたいとする。この時普通に書くとprevState.a.b.e = 3
だが、これだとprevStateの値が変わってしまっているため、すなわち状態変更前のオブジェクトに変更が加わっているため問題がある。
Immutableに変更するには次のようにeに向かいネストする度にprevStateの状態をコピーしていく。
const nextState = {
...prevState,
a: {
...prevState.a,
b: {
...prevState.a.b,
e: 3
}
}
}
これはObjectの上書きを上手く利用しており、例えば
const obj = {
a: 1,
a: 2
};
と定義すると、console.log(objB)
の結果は{a: 2}
である。すなわち同一のプロパティ名が存在したときには、下に書かれたものが上書きされるというルールをうまく使っていることになる。
ちなみに当たり前だがネストの度に前状態のコピーを怠って
const nextState = {
...prevState,
a: {
b: {
e: 3
}
}
}
console.log(JSON.stringify(nextState)); // {"a":{"b":{"e":3}}}
とするとprevStateのaプロパティの値が保持できず、fオブジェクトやcオブジェクトが消えてしまう。
パターンとしてはオブジェクトがネストされるたびにネストしたprevStateの値を上に放り込んでいくだけなので、何度かやっていけば機械的にできるようになるだろう。