React.kyoto v0.3.0 でのLT発表資料です。 https://react-kyoto.connpass.com/event/137847/
unstated-next による Redux に頼らない状態管理の考察React.kyoto v0.3.0 | Jul 19, 2019
View Slide
神保 嘉秀@jmblogじんぼ よしひで
今⽇話すこと• unstated-next の紹介• 気になる点や個⼈的な所感
unstated-next の紹介
https://github.com/jamiebuilds/unstated-nextunstated-next
Hooks の登場によって、Redux などの「状態管理ライブラリ」に頼らず、React の Context と Hooks だけで状態管理を実装することが可能になった。 unstated-next はその実装をサポートしてくれる、 必要最⼩限なライブラリ(わずか 200バイト)
unstated-next と unstated の関係• unstated というライブラリもあってちょっとややこしい。• 開発者はどちらも @jamiebuilds ⽒。• unstated は Context を活⽤したシンプルな状態管理ライブラリ。 2018年前半に発表された。• その後、React から Hooks がリリースされ、unstated のコンセプトをよりコンパクトに実現できるようになったため、Hooks ベースの API に⼀新してunstated-next という別パッケージで 2019年5⽉にリリースされた。
まず、Context と Hooks だけで状態管理を実装するとこんなコードになる
useState() あるいは useReducer() を使い、 state とその更新ロジックを内部に保持したカスタム Hook を⽤意する。
このカスタムHook をコンポーネントで直接利⽤すると、Hook 内の state はコンポーネントごとのローカル state となるが、Context で管理することで、同じ state の値を複数のコンポーネントで利⽤できるようになる。
にカスタムHookの中⾝を渡す。これにより、Contextの中⾝ = state が更新されると、コンポーネントが変更を検知して、再描画するようになる。
コンポーネントでは useContext() を使って、 Context の中⾝ = state を取得し利⽤することができる。
これだけで⼗分機能する。
state logicsこういうイメージContextcustom hook
Context を Container(容器、⼊れ物)と呼ぶと、よりイメージしやすくなる。Containerstate logicscustom hook
さっきのコードを unstated-next を使って書き換えてみると、こうなる。
createContainer(customHook) で Container を作成し、カスタムHookを格納する。
Container は Context と同様、Provider を持つ。
コンポーネントでは useContainer() を使って、 Container の中⾝ = state を取得し利⽤することができる。
これだけ。
unstated-next を使わなくても⼗分シンプルだが、unstated-next による「Container」という概念を使うことでよりわかりやすくなる。
Redux よりも優れている点• ファイルサイズは 1/40• 学習コストが圧倒的に低い(Context と Hooks さえ知っていればいい)• ほぼ素の React なので、どんなライブラリとも連携しやすい• パフォーマンスの⾯でも有利
Redux よりも優れている点• ファイルサイズは 1/40• 学習コストが圧倒的に低い(Context と Hooks さえ知っていればいい)• ほぼ素の React なので、どんなライブラリとも連携しやすい• パフォーマンスの⾯でも有利 ← どういうことか?
Redux Storecontainer component container component container component container componentRedux では、State が更新されると、マウント中の すべての Container Component(Storeとつながっているコンポーネント)が、変更の通知を受けとって、 mapStateToProps(あるいは useSelector) を実⾏するmapStateToProps() mapStateToProps() mapStateToProps() mapStateToProps()
Redux Storecontainer component container component container component container component処理の遅い mapState が1つ存在していると、state が変更されるたびに毎回実⾏されるため、アプリケーション全体のパフォーマンスを落とす重⼤なボトルネックとなる。mapStateToProps() mapStateToProps() mapStateToProps() mapStateToProps()
Redux Storecontainer component container component container component container component「⽬に⾒えて遅くはないが、微妙に遅い」という mapState でも、たくさん存在すると、やはりパフォーマンス劣化の要因となってくる。mapStateToProps() mapStateToProps() mapStateToProps() mapStateToProps()
reselect によるメモ化など、回避するためのベストプラクティスは存在するが、そもそも、グローバルな単⼀の Store にすべてのコンポーネントが依存しているという構造が引き起こす問題。
component component component component componentcontainer container container container次のように、Container (≒ Store) を複数に分けて、コンポーネントが本当に必要としている場合だけ共有するようになっていれば、この問題は避けられる。
component component component component componentcontainer container container containerもしパフォーマンス上問題のあるコンポーネントが存在していても、⾃分に関係のない state が変更されたときは何も起こらないので、コンポーネントが与える影響範囲は限定される。
unstated-next では、 Redux のようにアプリケーション全体の State を⼀つの巨⼤な Container で管理するのではなく、適切な粒度に分割して管理することが推奨されている。ただし、Container の粒度に関する制約やルールは 存在していないので、⾃分でルールを決めて運⽤する必要がある。
気になる点や個⼈的な所感
Container の最適な粒度は?• ⼩さすぎると管理が煩雑になりそうだし、⼤きすぎると Redux が抱えるのと同じ問題を引き起こす危険性が増す。• まずは、ある程度の機能ごとに分割してみて、少しづつ調整していく感じになりそう。
Container の最適な粒度は?• 正規化(normalize)されたデータは判断しやすい。 例えば、製品データ を「byId」と「visibleIds」に正規化したとすると、それぞれを Container にすれば、 と で必要とする State が良い感じに分離できる。
Redux で Store を複数に分割すればいいのでは?• Redux の FAQ に回答が載っている。 可能は可能だけど、Redux DevTools とか使えなくなるし、単⼀の Storeで使ってもらうことを想定している、とのこと。 https://redux.js.org/faq/store-setup#can-or-should-i-create-multiple-stores-can-i-import-my-store-directly-and-use-it-in-components-myself
「Provider ⼊れ⼦地獄」にならないか?• 多くの Container を必要するコンポーネントでは の⼊れ⼦がかなり深くなる。
「Provider ⼊れ⼦地獄」にならないか?• helper 関数を⽤意するぐらいしか回避策はなさそう。 https://github.com/jamiebuilds/unstated-next/issues/35#issuecomment-500255562• ⼊れ⼦があまりにも深い場合は、Container の粒度が細かすぎるか、コンポーネントの責務が⼤きすぎる可能性が考えられるので、 リファクタリングのポイントといえるかも。
最適化のコツは?• Redux での「reselectを使いましょう」的な、unstated-next ならではの最適化テクニックは特にない。• 素の React とほぼ同じなので、React のスタンダードな最適化がそのまま使える。 https://github.com/jamiebuilds/unstated-next#tip-3-optimizing-components
デバッグはどうする?• 専⽤のデバッグツールはない。• React Developer Tools に Hooks のサポートが⼊っていて、最新の stateの値は確認できるが、変更履歴などは確認できない。 https://github.com/facebook/react-devtools/pull/1272• これが⼀番つらいところかも。
今すぐ Redux を捨てて unstated-next に乗り換えるべきか?• Redux よりも簡単にコーディングできるのは間違いないが、Redux とは別の全体設計⼒が試される。• 既存のアプリケーションに導⼊するのであれば、パフォーマンスチューニングの⼀環として、書き換えが頻発する State の⼀部を unstated-next に置き換えてみるのはありだと思う。
スケールするのか?(⼤規模アプリでも使えるか?)• Redux を使って安定した⼤規模アプリケーションを実装できる⼒量があれば、unstated-next を使ってもうまくいくのではないか。• 個⼈的には、最適なアプリケーションのアーキテクチャを探っていきたい。
スケールするのか?(⼤規模アプリでも使えるか?)• Container はクリーンアーキテクチャのPresenter(ユースケースやエンティティのデータ形式を変換して、外界である UI に伝える役割)にあたるように思うが、React にがっつり依存しているのでこのレイヤーなのか少しモヤモヤする。• クリーンアーキテクチャに適⽤するならunstated-next ではなく unstated のほうが ぴったりはまりそうな印象。
もし実戦投⼊してノウハウが溜まったら、ぜひシェアしてください!
おわり