Reactの状態管理の変遷に関する自分史 From 2014 To 2022
はじめに
2014年にReactを触りはじめて以降、2022年現在まで集中の度合いにバラツキはあるものの、ずっとReactでなんらかのアプリケーションを書いてきました。
その中で様々なアーキテクチャや設計に関する議論がありましたが、特に状態管理についての変遷を自身の体験をもとにまとめてみたいと思います。
多分に昔話的な内容なものの、適度に読み飛ばしてもらいつつ、Reactの状態管理のやや偏った歴史と現在地点の認識の共有になればと思います。
2014- | Reactの導入 - Flux
SPA
iPhone 4Sが出てスマートフォンを持つ人も多くなり、エンジニアでなくても多くの人が日常的にGmailやMapアプリケーションに触れるようになった時期だったと記憶します。
Webアプリケーションの構築でもフロントエンドへの要求レベルが高くなっていた感覚があり、JavaScriptで動的なViewを構築するケースが増え、自分自身もここに注力していた時期でした。
当時はSPAという言葉を知らず「Submitしない、画面が真っ白になることがない画面を作りたい」と考えていた記憶があります。
当時実装に使っていたのはjQueryとBackbone.jsでしたが、Reactの他にもKnockout.js、Vue、Angular.jsなどのライブラリを手当たり次第に試して、MVVMや双方向バインディングの概念をみつつも本番導入していくものを決めかねていた状態でした。
仮想DOMと単一データフロー
自分自身がまだReactのアーキテクチャに関する理解が不十分だったところに出てきた、mizchiさんの「なぜ仮想DOMという概念が俺達の魂を震えさせるのか」は自分には強烈なインパクトをもたらしました。
当時jQueryやBackbone.jsでの実装で自分が悩んでいた課題は、MVCでのModelとViewの同期(イベントのハンドリング -> Model更新 -> ViewのRender)のためには、パフォーマンスのためにViewの差分のみを更新する必要があるものの、差分を正確に考えて実装するのは難易度が高く、結果バグを埋め込むという点でした。
それに対してReactとFluxの根底にある、単一方向のデータフローとそれでも宣言的UI・Virtual Domによる差分更新によってrenderを抑制可能なアーキテクチャは「これだ!」と思うものでした。
一方でFluxはライブラリが群雄割拠の状態でこれだ、と思えるものがまだなく、2015年に最初にReactを本番導入したタイミングではFluxライブラリは採用・実装しませんでした。上位のコンポーネントにStateを集中させ下位のコンポーネントに伝達させる、下位コンポーネントで発生したイベントは最上位まで伝搬する、というProps Drilling(Props のバケツリレー問題)上等な作りにしていました。
今振り返ると、ReactとFluxがセットとして紹介され、宣言的UI・Virtual Domと一方向のデータフローが強く自分の中の設計原則となっていたことが、後のHooks -> Colocation/Lifting State Upの理解を遅くしてしまった部分があると思います。
2016- ReduxとPresntational/Container Components
作成するアプリケーションも一定大きくなりはじめたので、Fluxの考え方のライブラリでも有力となってきたReduxを導入し、その中でのアーキテクチャ、設計パターンを考えるようになりました。
当時はDan Abramovの「Presentational and Container Components」を度々参照していました。
Storeに接続するContainer Component/Presentational Componentを明確に分ける方針がその軸となっており、StoreとConnectされた部分を絞ることで関心事が分離でき、再利用性が高まる、という趣旨です。
記事内にもある通りこの考え方自体は画期的な新しいものだったというよりも、明文化してパターン化された事に価値があったと感じます。
自分が最初期にFluxライブラリを使わず実装した構成も同じ方向でしたが、「Stateを持つコンポーネントを絞った方が保守しやすい」くらいのゆるやかな理解で実装していました。
これ以後の時期の自分にとっての大きなトピックはTypeScriptの導入でしたが、状態管理については大きなアーキテクチャ面での変化があったというよりもReduxのミドルウェアなどよりアプリケーション設計的な内容が話題にあがっていた印象があります。
間に | GraphQLとApollo Client
2018年から1年ほどGraphQLとApollo Clientを採用したReact Nativeのモバイルアプリの開発をしていました。
GraphQLについては以前から関心があり少してを動かして検証したことがあるものの、Apolloに関する理解はありませんでした。
そのタイミングで読んだ「世のフロントエンドエンジニアにApollo Clientを布教したい」は自分にとってApollo Clientについての理解をぐっと押し進めるきっかけになりました。
Apollo ClientはHTTPでいうfetchやAxiosといったGraphQL ClientとReduxのようなStoreがバンドルされたライブラリです。
後にSWRやReact QueryのAPIキャッシュを初めてみた時には、Apolloに近いなという感覚を持ちました。
さらにApollo ClientはAPIに依存しないものもLocal Stateとして管理でき 、 それらを統一的にQueryで取得できます。
この開発体験は現状他のライブラリではなかなか実現できない部分だと思います。
State管理としてこれは大きな変化であったものの、今思うと改善できる部分があります。
当時はトップレベルのScreenでApollo Clientでデータを取得して、それを下位コンポーネントに伝達する、というPresentational/Container Componentsの延長上で設計を行っていました。
しかし後で触れるColocation/Lifting State Upの考え方を導入するのであればデータ表示するコンポーネントとQueryを近くに配置する設計がありえたと感じます。
2019- | Hooks -> Colocation/Lifting State Up
Hooksの登場
2019年2月リリースのReact 16.8からHooksが導入されました。
ただ登場当時、Hooks自体は状態管理に関する設計を大きく変更するほどのものではないと捉えていました。
2019年は引き続きReduxを採用しつつ、コンポーネントをClassベースからFunctionalコンポーネント + Hooksに変更する、程度の対応にとどめていました。
使っていたHooksはuseState・useEffect、パフォーマンス最適化でuseCallbackやuseMemoを使う程度でした。
Hooks前提の設計の検討
2020年-2021年に入り、Reduxを導入せずHooksを中心とした設計の議論が多く見られるようになり、自身でもHooksを前提とした設計を考えるようになりました。
Hooksの登場以降にReactに触れた人もチームに多くなる中で、自分が原則としていた単一データフローやPresentational/Container Componentsの分離についても考え直す必要があると感じるタイミングがありました。
当時参照したのはsadnessOjisanさんの 「脱Reduxを試みて失敗した話、その原因と対策について」 や 「Context API と useReducer で custom hook を作る時のテンプレート」でした。
Context APIとuseReducerでcustom hookを作っていく構成を自分でも作ったのですが、これはチーム開発の中で各人で実装がブレがちでなかなか運用にのせることができませんでした。
Colocation/Lifting State Up
React公式でも記述のあるColocation/Lifting State UpはState設計に関する大きな指針となると考えています。
端的に言えば、以下の方針の変更を行いました。
- Before: PageやScreenといった最上位のコンポーネントをContainerとしてデータの取得を集約、下位のPresentational Componentsに渡す
- After: 表示のためのデータ取得と描画をできるかぎり近くによせて管理する、必要に応じてStoreの場所を上位に移動したり、ContextやGlobalなStoreに移動する
Kent C. Doddsの「State Colocation will make your React app faster」はColocationについて理解する最初の一歩になりました。
Hooks以降、自分の中でまとまったアーキテクチャ・アプリケーション設計の指針を持てずにいたのですが、最近「Design Doc for react-boilerplate-2022」として形にすることができたので、そちらもご参照いただけると嬉しいです。
Discussion