採用している状態管理パターンを言語化する
私は2017年ごろからReactをメインに開発しています。そしていろいろな状態管理のパターンを経験してきましたが、最近あるパターンに落ち着いてきたように思います。
この記事では、まず私が採用している状態管理パターンについて書きます。
それからなぜそのパターンに落ち着いたのかを説明したいと思います。
React.useState
を第一に検討する
状態管理については次のように考えています。
- 状態管理を避けられないか
- 基本は
React.useState
- Jotaiは最小限に
状態管理を避けられないか、については過去に記事を書いています。
管理している状態を1つでも減らして、開発スピードを維持していきましょう。
まず、この発想がスタートラインです。まず状態管理が必要かどうかをしっかり検討します。
そして基本的に状態管理にはReact.useState
を使っています。
1つのコンポーネントに閉じている状態を扱うならReact.useState
を使うことに決めています。
複数コンポーネントから状態を扱う必要がある場合はJotaiを使うことにしていますが、そういうコードを読むコストは高いです(1つのコンポーネントに閉じている状態を扱うコードに比べて)。そのため、できるだけ複数コンポーネントから状態を扱う設計を避けるようにしています。
Redux Toolkitライクにコードを管理する
ここまでに説明では採用している状態管理のパターンにRedux Toolkitは登場しません。
しかし過去に採用していたRedux ToolkitのcreateSlice
でreducers
をまとめて記述する方法は、とても可読性の高い優秀な方法だったと思っています。
そのためReact.useState
を使う場合、次のようなカスタムフックで状態を操作する関数をまとめて記述しています。
const useCounter = () => {
const [state, setState] = React.useState(0);
return React.useMemo(
() => ({
state,
increment: () => setState(c => c + 1),
decrement: () => setState(c => c - 1),
}),
[state],
);
};
export default function FooComponent() {
const counter = useCounter();
...
}
setState
はカスタムフックの外ではアクセスできないので、用意した関数以外の方法で状態が更新されることはないことがすぐに分かります。カスタムフックはReactコンポーネントと同じファイルに記述します。
Jotaiについても同様の考え方でコードを扱います。atom
の定義にはexport
を付けずに、具体的なケースごとの状態操作を実装したフックだけをexport
します。
私は状態を操作する関数がまとまっているコードの様子を、Redux Toolkitへのリスペクトを込めて「Redux Toolkitライク」と呼んでいます。それはつまり状態管理のカプセル化です。
以上が現在採用している状態管理のパターンです。
なぜこのパターンなのか
なぜこのパターンに落ち着いているのかきちんと言語化せずにいましたが、以下の記事に言語化のヒントがありました。
コンポーネントが自立して機能するよう設計することは、スケーラビリティーを生みます。
上述したReact.useState
の使い方は状態の扱いが1つのコンポーネントに閉じており、自立しているとみなすことができます。
ある状態を複数コンポーネントから扱っているということは、それらのコンポーネントは自立していません。ある状態と、関係する複数のコンポーネントを1つのコンポーネント群として扱うことで自立しているとみなすことができますが、コードリーティングの負担は大きくなります。つまり状態のロジックが、関係するコンポーネントをくっ付ける接着剤のように働いてしまうということです。
Jotaiは、Reduxとは異なり状態を分散管理することができるため採用しています。しかしそれでも複数コンポーネントにimport
すれば、それらをコンポーネント群として扱う必要があるため、できるだけそういった設計を避けています。
まとめ
分散を意識して設計することが大規模アプリのスケーラビリティーにおいて重要!以上。
Discussion