はじめに
Reduxを使わなくても、ReactでSPAをつくることは可能です。
画面の初期化時に一度だけ叩くapiをreduxで管理したことはありませんか?
apiを一度叩いて画面に表示するだけなのに、reducer/action/actionCreatorを作ってconnectをしてませんか?
Reduxを無理して使っていませんか?
自分が参画したWebアプリケーション開発では、Reactが採用される場合、常にReduxもセットになっていました。
Reduxは要件によってはオーバーエンジニアリングかもなーと感じることが多いので、改めてどんな時にReduxが有効かのか自分なりに考えてみました。
SPAの設計で大事なこと
自分がフロントエンドの設計で大事だと思うのは、「ViewとStateの分離」です。複雑なアプリケーションでこれが出来ていないと可動性が低く変更しづらいです。
Viewはコンポーネントと呼ばれたりしますが見た目の部分です。
Stateはコンポーネントの状態です。本記事では、状態を変化させるロジックも含めStateと呼びます。
個人的には以下を実現することが重要です。
-
ViewとStateが密結合にならず、コードの可読性を保てる。
-
ユーザーが複雑な操作をしても、アプリケーションの状態が予測可能になっている。
どんな時にReduxが有効か
Reduxが実現しているFluxアーキテクチャは、状態をStoreに、ロジックをAction/ActionCreatorにすることで、アプリケーションの状態を予測可能にしています。
Reduxを使うことで「ViewとStateの分離」は達成出来ます。
ですが、Reduxを使わなくても「ViewとStateの分離」は達成出来ます。
Redux辛いみたいな話はわざわざReduxで解決しなくても良い問題をReduxの冗長な処理を書いて解決しているからだと思います。
では、Reduxを使わないと辛い時はどんな時でしょうか?
自分は以下だと考えます。
- コンポーネントの階層が深い
- 画面で発火するイベントが多い
componentの階層が深い
propsのバケツリレーと呼ばれる問題です。prop drilling
の方が有名でしょうか。
出典:Learn React Context in 5 Minutes - A Beginner's Tutorial
Reactではコンポーネントにデータを渡す時にpropsを使います。
コンポーネントの階層が深いと、親コンポーネントから子コンポーネント、さらに孫コンポーネントと渡したいコンポーネントまで延々とpropsをバケツリレーのように渡していく必要があります。
このバケツリレーを省略するためにReduxを使うのは有用です。
自分は3回を超えたらRedux導入を考えます。
画面で発火するイベントが多い
1つの画面でユーザーがやれることが多い画面はReduxが有効だと判断します。
例えば、SlackやGoogle Carenderは1つの画面で様々なイベントを発火でき、リアルタイムにUIを変更しつつapiリクエストを飛ばします。
様々なイベントを高速でハンドリングする場合は、アプリケーションの状態が予測しづらくなるのでReduxを使います。
Reduxはdevtoolが強力でstoreに対する変更を全て記録してくれるのでデバッグもしやすいです。
Custom Hookで「ViewとStateの分離」を実現する
一覧画面等で、画面初期化時に一度だけfetchしたデータを表示するのにReduxを使うのはオーバーエンジニアリングです。
自分はReactのCustom Hookを使います。
以下はユーザー情報を表示するだけの架空の画面の例です。
Custom HookにState(状態とロジック)を切り出します。
Custom HookはUtil的な使い方だけでなく、ドメイン固有の状態とロジックを管理するのにも有効です。
export const useUser = (): [
User | undefined,
() => void
] => {
const [user, setUser] = React.useState<User | undefined>(undefined);
const [errorMessage, setErrorMessage] = React.useState<string | undefined>(undefined);
const fetchUser = async (): Promise<void> => {
try {
const res = await axios.get<User>('/api/user');
setUser(res.data);
} catch (e) {
setError(e.response.date.message)
}
};
return [
user,
errorMessage,
fetchUser,
];
};
stateとstateを操作するロジックをuseUser
というCustom Hookに分離しています。
Reduxのreducer/action/actionCreatorをまとめたduckに近いですが、少ない記述量で「ViewとStateの分離」が出来ています。
import { useUser } from 'useUser';
export const UserContainer: React.FC = () => {
const [user, errorMessage, fetchUser] = useUser();
React.useEffect(() => {
fetchUser();
}, [fetchUser]);
return (
<UserComponent
user={user}
errorMessage={errorMessage}
/>
);
};
Custom Hookは任意のcontainer/component層でデータとロジックを注入できます。
自分はいつもcontainer層で注入します。
Reduxを使うならアプリケーション全体で、Stateの管理方法をReduxで統一したいと思ってた時もありましたが、最近は要件によってCustom HookとReduxを使い分けるのが良いかなと考えています。
Custom Hookの中でapiリクエストをする場合、middlewareを気にしなくて良いです。
今のところ上手くいってますが、同じ画面やドメインでCustom HookとReduxの両方を使わないようにしています。
まとめ
Reactでは「ViewとStateの分離」をいくつかの方法で実現出来ます。
とりえあずReduxにすると大きく外すことがないですが学習コストやコード量が大きいので、Reduxでオーバーエンジニアリングになりそうな時はCustom Hookがおすすめです。
参考
JavaScript: Reduxが必要なとき/不要なとき(翻訳)