Open8

ReactのSignalとAtomic State Managementの動向を追う

koushisakoushisa

導入

最近のReactの状態管理ライブラリがスケールした先はstore自体をVanilla化して脱Reactする傾向にある。
(Zustand, Jotai, tanstack query, ...etc) [1][2]

理由として、Reactのコンテキストでstoreを持つと以下のような問題がある。

  • useMemo, useCallbackなど本質的にノイズなコードが必要
  • Reactのimmutableな制約の影響を受ける

storeをVanillaにすると

  • storeに対するget, setを自然に表現できる
  • インスタンスが変わらない(参照が同じ)なのでレンダリングのチューニングの余地がある

そして火付け役としてuseSyncExternalStoreや、useフックがReact公式から提供された。
これを利用するとVanillaでもReact18以降のセマンティクスに対応してtransition, concurrentのメリットを享受できる。
もはやVanillaでstoreを持つことのデメリットは消えつつある。
以前と比較してstoreとReactを連携するためのルールやコードは遥かに減っている。

Vanilla化するとコードベース全体もシンプルになっていく。
というのも、プログラムの複雑性はドメインモデルと技術基盤の乖離の係数(いわゆるインタフェースを合わせるためのボイラープレート)によって増すという解釈(筆者独自)からすると、storeの基盤をVanillaにすると技術基盤の乖離が減り、ドメインモデルをより純粋かつ自然に表現できるようになるからだ。
Reactにおけるドメインモデルと技術基盤の乖離についての具体例は https://zenn.dev/link/comments/238de1fb4a497d に詳細を記している。
React Hooksでドメインモデルを表現することには限界がある。

界隈として、今まで本当はstoreをVanillaにしたかったが、Reactの制約によって難しかったのだろう。
それがReactの進化によって可能となった、とも言える。
useSyncExternalStore, transition, concurrentなど、ReactのDXとUXを両立させる進化はめざましい。
そのような背景から状態管理を脱Reactする流れが生まれてきたのだと思う。

それが結果的に設計をシンプルにし、Islands ArchitectureのようなReact以外のエコシステムを取り込んで選択肢を増やすきっかけにもなる。

論点

Vanilla化したstoreに対しreactivityをどう実現するかが論点となるが、大まかに2パターン存在する

  1. observer, proxyを組み合わせる (ここでは便宜上signalパターンと呼ぶ)
  2. ReactのuseSyncExternalStoreを使うパターン

本スクラップでは前者のsignalパターンの動向を追う。
なるべく中立的に各ライブラリの思想とトレードオフを挙げていき、signalの理解を深める。
また、個人的にAtomic State Managementを選定することが多いため比較対象としてRecoil, Jotaiを挙げている。

登場人物

Atomic State Management

Signal

関連するReactのコア機能

  • パラダイムシフトの可能性を秘めているモノたち

  • React Forget

    • まだ研究中
    • ReactのコンパイラでuseMemoを動的に作るやつ
  • use

    • promiseを第一級オブジェクトとして扱う
    • 将来的にはcache apiという形でstoreを持つ予定
  • Support Promise as a renderable node

    • promiseをレンダリング可能なノードとして扱う
脚注
  1. Form系ライブラリはあまりVanilla化されてないが、これはフォーカス管理などでrefが必要になることに起因している?作ろうと思ったら作れそうだが、複雑なForm State管理の需要は低いのか? ↩︎

  2. この領域の先駆者は dai_shi 氏と Tanner Linsley 氏だと思う。https://twitter.com/dai_shi/status/1434543349524877317, https://twitter.com/tannerlinsley/status/1504907291291623427 ↩︎

koushisakoushisa

jotai-signal

koushisakoushisa

legend-state

まだ調べきれてないこと

  • Suspense対応
  • testing

所感

  • 第一印象としてはRecoil, Jotaiよりも柔軟性が高くて抽象化の筋が良いと思った
  • アプリの設計の文脈でいうと、スケールに合わせてreducerやaction的な概念でステートをひとまとめに更新したくなるので、そのあたりの知識は普遍的に必要
    • 最初にポリシー定めておくのは大事
      • いつでもどこでもmutableに変更しちゃうと後から破綻する
      • MVVM時代に後戻りしないように
    • コンポーネントを横断する際は適切にlifting state upするとよさそう
koushisakoushisa

Reactのコアチーム(useRFC書いていた)人は、signalはUIコードの書き方としてはあまり良くないと思っているらしい。

https://mobile.twitter.com/acdlite/status/1626590880126889984?cxt=HHwWgMDTzbLy55ItAAAA

We might add a signals-like primitive to React but I don’t think it’s a great way to write UI code. It’s great for performance. But I prefer React’s model where you pretend the whole thing is recreated every time. Our plan is to use a compiler to achieve comparable performance.

When we do add a signals-like primitive, it’ll mostly be geared toward serving as a compiler target, or as a low level API for library authors

https://twitter.com/acdlite/status/1628811935088013314

仮にReactにsignalのようなリアクティブプリミティブを追加したとしてもそれは内部に隠蔽するのだろう。
自分もUIのプログラミングのメンタルモデルでは毎回全部作り直したことにするReactのイミュータブルなメンタルモデルに共感しているので、この意見は賛成する。

signalがUIのレイヤに登場するとコンポーネントの状態を司る重要なデータ構造が露出してしまい秩序を設けるのが難しくなる。

コンポーネントのレンダリングとそのデータの依存グラフの構築を分離するAtomic State Managementを支持しているところにもこのような理由がある。