https://zenn.dev/peaske/articles/38f1f7cce74dbc

og-base_z4sxah.png

はじめまして、ピースケです。

前のインターン先で、Redux系のライブラリからCustom Hooksを使ったステート管理方法へと移行する事になり、useReducer+Contextで実装をし始めました。しかし、Contextのレンダー回数の抑止策にステート更新系とステート参照系のロジックを切り分けたり、useReducerを型でゴリゴリに固めていく中で、コードがかなり冗長で可読性に乏しい実装になりつつありました。また、Contextの独特な実装方法が陳腐化したことによって開発負債を生むのではないかという懸念や、車輪の再開発を行っているような不安も生じました。そこで私は、Contextの代わりとなるステート管理ライブラリの調査を任されました。

今までブラックボックスに感じていたステート管理ライブラリのZustandやJotaiの蓋を開けてみると、意外とコード量が少なく、読みやすい物となっていたので、驚きました。

実装方法は異なるにせよ、自分もシンプルなものであれば作れるのではないかと思い、butcher.jsを作ってみました。

ステート管理の仕組み

React-ReduxやZustandのようなステート管理ライブラリは、裏で多くの事をこなしてくれていて、純粋なステート管理だけでなく、非同期処理の対応や他のライブラリ(immerなど)との連結なども行ってくれています。

しかし、一番基本な部分に戻ると、ステート更新のステップは以下のように要約することができます。

1. ステートの作成・更新する2. ステートの更新に購読し、更新時の副作用を渡す3. ステート更新後に購読先に副作用を発火させる(主にDOMのアップデート)

フロントエンドのステート管理に、単純なObjectではなくステート管理ライブラリを使う理由は、ステートを更新する事に留まらず、更新を反映するようにDOMもアップデートさせたいからです。

Reactで使うステート管理ライブラリは、2番をReactのレンダー、3番をuseSyncExternalStoreというReact提供のhooksで処理していますが、butcher.jsはvanilla Javascriptのために作ったので、それぞれを働きを分かりやすく見ることができます。

では、順番に追っていきましょう。

useSyncExternalStore hookはまた別の機会に試してみようと思います。

※ React-Redux

useSyncExternalStoreをReact-Redux内部の独自Hooksに渡し、そのReact-ReduxがReactのステートに作用します。initializeConnectの実装自体コメントが多く据えられており、ライブラリ製作者の創意工夫が見受けられるのですが、ここでは割愛します。

// react-redux/src/index.ts

import { useSyncExternalStore } from 'use-sync-external-store/shim'
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector'

...

initializeUseSelector(useSyncExternalStoreWithSelector)
initializeConnect(useSyncExternalStore)

...

※ Zustand

Zustandは、React-Reduxと同じように、useSyncExternalStoreWithSelectorのwrapperのhooksをライブラリ消費者に提供しています。