🤖
Reactのオブジェクト配列でバグったのでimmutabilityについて復習
結論、自分がいまmutateしているのかそうでないのか気をつけようという話でしかないですが!
データの変更の仕方は少なくとも2通りに分けられる
- データの値を変更したいときに、下記2通りの変更の仕方がある。前者は特にmutate(書き換え)と呼ばれるが、後者は特に何と呼ばれるわけではない
- いまデータが存在するメモリアドレスに格納されている値を、直接書き換える
- いまデータが存在するメモリアドレスに格納されている値を、他のメモリアドレスにコピーしてから、コピーしたデータを書き換える(もちろん、変更後のメモリアドレスを、当該データを参照しているモジュールに何らかの手段で伝える必要がある)
データのタイプとしてのmutable/immutable
- mutateできるデータはmutable、そうでないデータはimmutableとされる
- TSにおいてはプリミティブ型データはimmutableであり、オブジェクト型データはmutableである
推奨される操作としての、immutable的取り扱い
- 「mutableであること」と「mutateすべきであること」は異なる
- 特にReactにおいてはimmutabilityを保つ(すなわち「一度作ったデータは直接変更しないこと」)が推奨されている。主要な理由として下記がある(詳しくは下のチュートリアルを見てね)
- オブジェクト間の変更の検出が低コストかつ容易になる。結果として再レンダリングのタイミングが制御しやすくなる
- 履歴の保存をはじめとした、複雑な仕様を実装しやすくなる傾向にある
オブジェクトの配列を扱う際の注意
-
オブジェクトの配列を扱う際は、配列自体が持っているデータはオブジェクトへの参照であることに注意する
- 例えばオブジェクトの配列がコピーされると、配列自体は別の2つのもの(内容は同じ)がメモリ上に存在することになるが、それぞれは同じ参照を持っている。そのため片方の配列で要素オブジェクトの変更をすると、もう一方の配列でも当該要素への変更が反映されることになる
- これにコンポーネント間のpropsの受け渡しが絡むと、一見すると分かりづらいバグとなるため注意が必要
- 例えばオブジェクトの配列がコピーされると、配列自体は別の2つのもの(内容は同じ)がメモリ上に存在することになるが、それぞれは同じ参照を持っている。そのため片方の配列で要素オブジェクトの変更をすると、もう一方の配列でも当該要素への変更が反映されることになる
-
オブジェクトの配列に変更を加える際にimmutabilityを保つ(すなわち「一度作ったデータは直接変更しないこと」)には、下記2通りの方法がある
- immerライブラリを使用する。例えばproduceは便利
- スプレッドにより、変更したい要素であるオブジェクトを適宜コピーして処理する
array = [{name: "a", value: 1}, {name: "b", value: 2}] //元のarrayまで変更されてしまうやり方 //arrayをコピーしているけど、参照は同じなのでarrayの要素も変更を受ける newArray = [...array] newArray[0].value = changedValue //元のarrayの変更は回避するやり方① //arrayをディープコピーした配列が生成され、 //それがcopiedArrayとして第2引数の関数で処理された結果が返ってくる newArray = produce(array, (copiedArray) => { copiedArray[0].value = changedValue }) //元のarrayの変更は回避するやり方② //arrayやその0番目の要素を展開してコピーしつつ、変更をする newArray = [...array] newArray[0] = { ...newArray[0] value = changedValue }
Discussion