React のイミュータブルはそんなにいいのかな
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ イミュータブルにすると変更するのが面倒
◆ この点は Vue のほうがいい
◆ イミュータブル前提な言語だともっと楽なのかなと思ったけどそうでもなさそう
◆ この点は Vue のほうがいい
◆ イミュータブル前提な言語だともっと楽なのかなと思ったけどそうでもなさそう
React のイミュータブルは更新が面倒
数時間もあれば作れそうなシンプルなツールを React と Vue で作っていて 比較するとやっぱり React のイミュータブルは面倒だなと感じましたよくある配列の要素から指定のものを削除したり更新したりするところで React だと
const remove = remove_index => {
setState(state.filter((_, index) => index !== remove_index))
}
const change = change_index => event => {
setState(state.map((item, index) => index === change_index ? event.target.value : item))
}
という感じの処理が必要になります
配列の要素のひとつを置き換えるのではなく 配列の要素ひとつの中のプロパティを書き換える場合はさらに面倒です
// [{ foo: 1, bar: 2, value: 3 }, ...]
setState(
state.map(
(item, index) => index === change_index
? { ...item, value: event.target.value }
: item
)
)
もっとネストが深いと
// [{ foo: 1, bar: 2, value1: { baz: 3, value2: 4 } }, ...]
setState(
state.map(
(item, index) => index === change_index
? { ...item, value1: { ...item.value1, value2: event.target.value } }
: item
)
)
Vue の場合は Proxy を使ってリアクティブオブジェクトとなっているので そのままオブジェクトを書き換えれば変更されたことが Vue に伝わるので単純にオブジェクトのプロパティを更新すればよいです
state.splice(index, 1)
state[index] = event.target.value
state[index].value = event.target.value
state[index].value1.value2 = event.target.value
とてもシンプルで見やすいです
Vue も合わせて使っていると この点では React がとても不便に感じます
イミュータブルの利点は
変数を const にしてイミュータブルにするなどでは あとから変更されないことによるイミュータブルの利点を感じますが オブジェクトのプロパティでまでする必要があるのか疑問です同じ state オブジェクトならプロパティが変わらないことが保証されていても state オブジェクト自体が別になるわけなので const 変数のときのようなメリットは感じられないです
基本変更されない前提のものならともかく アプリケーション状態のように頻繁に変更されるところをイミュータブルにしても不便なことのほうが多いです
イミュータブルを前提とした Redux や Elm の機能でタイムマシーン機能がありますが 実際にそれが必要になることはほぼ無いと思います
過去のすべての状態を上書きせず個別に保持しているのでユーザの操作を完全にトレースして再現できますが それをしたいことってあるのでしょうか
React で楽にするには
setState だけ見ればイミュータブルにしなくても動くと言えば動きますstate.foo.bar++
setState(state)
だと state は同じなので画面が更新されませんが state が別物であればいいので
state.foo.bar++
setState({ ...state })
とできます
しかしこれだと state.foo はその bar プロパティが変わっているのに同じオブジェクトです
state.foo を useEffect や useMemo の依存配列に渡したり React.memo が使われてるコンポーネントの props に渡すと変更なしとみなされ正しく動きません
サードパーティライブラリには 変更用オブジェクトを書き換えたらその書き換え情報を基に本来のイミュータブルオブジェクトから新しいイミュータブルオブジェクトを作ってくれるものがあります
一見便利ですが 中で特殊なことをしてるのはわかりますし イミュータブルにするためだけにこういうサードパーティライブラリを使うのもどうなのかなと感じます
使うとすれば React のコアに取り込まれて標準機能として
setState(state => {
state.foo.bar++
})
みたいなことができるようになったらでしょう
イミュータブル前提な言語だと
JavaScript 自体がタプルやレコードといったイミュータブルな配列やオブジェクトをサポートしていないです言語として想定された書き方から離れるから面倒が増えている気もします
じゃあイミュータブルが前提な言語だと どんなふうに書けるんでしょう
Elm ってどう書いたっけと思って比較してみると JavaScript の
{ ...foo, bar: 1 }
は Elm では
{ foo | bar = 1 }
でした
だいたい同じです
ネストする場合に
{ foo | bar.baz = 1 }
のように書けたら良かったのですが これはできません
それどころか
{ foo | bar = { foo.bar | baz = 1 } }
の foo.bar もできません
. 付きを書けないので一旦別変数に foo.bar を入れることになります
思ったより不便でした
実際にはヘルパ関数を用意したりパイプライン演算子の機能を使ったりしてネストした値を更新してるようです