LoginSignup
7
5

More than 3 years have passed since last update.

無理してReduxを使わずに、Custom Hookで楽に状態を管理する

Last updated at Posted at 2020-02-16

はじめに

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の方が有名でしょうか。

ey25z0hvmy31xiiqqwgq.png
出典: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的な使い方だけでなく、ドメイン固有の状態とロジックを管理するのにも有効です。

useUser.ts
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の分離」が出来ています。

container.ts
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が必要なとき/不要なとき(翻訳)

Learn React Context in 5 Minutes - A Beginner's Tutorial

Fluxとはなんなのか

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5