翻訳: なぜReduxを使うのをやめたのか

Gabriel Abud氏の記事「[Why I Stopped Using Redux](https://dev.to/g_abud/why-i-quit-redux-1knl)」を、ご本人の許可を得て翻訳したものです。

比較的小規模なSPAであればこの記事に書かれているようにReduxはtoo muchな技術かなぁと個人的に感じます。
本記事へのコメントもなかなか賑やかでして、参考になるコメントが多くあります。ぜひ記事を読んだ後でコメントも確認してみてください。

なお、誤訳がありましたら編集リクエストを頂けると幸いです。

---

## まえがき
[Redux](https://redux.js.org/)はReactエコシステムにおいて革命的な技術でした。Reduxにより[イミュータブルなデータ](https://medium.com/dailyjs/the-state-of-immutability-169d2cd11310)をグローバルストアに保持できるようになり、コンポーネントツリーにおける[prop-drilling問題](https://kentcdodds.com/blog/prop-drilling)を解決しました。アプリケーション内でイミュータブルなデータを共有するために、拡張性の優れたツールであり続けています。

しかし、そもそもなぜグローバルストアが必要なんでしょうか？フロントエンドアプリケーションはそんなに複雑なのでしょうか？それとも単にReduxを使いすぎているのでしょうか？

## シングルページアプリケーションの問題点
Reactといったシングルページアプリケーション（SPA）の出現はWebアプリの開発方法に多大な変化をもたらしました。バックエンドとフロントエンドを分離することで専門化や問題の分離が可能になりました。さらに状態まわりに多くの複雑さをもたらしました。

非同期でデータをフェッチすることは、フロントエンドとバックエンドの2つの場所にデータが存在しなければならないことを意味します。ネットワークレイテンシを減らすためにデータのキャッシュを維持しながら、全てのコンポーネントで利用できるようグローバルにデータを保存する最善の方法を考える必要があります。今や状態バグや[データの非正規化](https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape#normalizing-state-shape)、最新でないデータに悩まされずにどのようにグローバルストアを維持するか検討することがフロントエンド開発の大部分を占めています。

## Reduxはキャッシュではない
Reduxや類似の状態管理ライブラリを使う時に多くの人が陥る主な問題は、Reduxをバックエンドの状態のキャッシュとして扱うことです。データを取得し、reducer/actionでストアに追加し、最新の状態であることを確認するために定期的に再取得します。Reduxに多くのことをさせて、課題に対する包括的な解決策として利用しがちです。

覚えておくべき重要なことの1つは、フロントエンドとバックエンドの状態は決して同期していないことです。せいぜい同期しているかのように見える程度です。これは[client-serverモデル](https://en.wikipedia.org/wiki/Client%E2%80%93server_model)の欠点の1つであり、そもそもcacheが必要となる理由になります。しかしキャッシュしつつ同期した状態を維持するのは非常に複雑になるので、[Reduxが推奨している](https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape#designing-a-normalized-state)ように、バックエンドの状態を1から作り直すべきではありません。

フロントエンド側でデータベースを再構築し始めると、途端にバックエンドとフロントエンドの責任の境界線が曖昧になります。フロントエンドエンジニアとしては、シンプルなUIを作るために[テーブルとその関係性](https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape#relationships-and-tables)に関する知識を持つ必然性はありませんし、[データを正規化する最善の方法](https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape#normalizing-nested-data)を知っている必要もありません。その責任はテーブルを設計するバックエンドエンジニアにあるべきです。バックエンドエンジニアはドキュメント化されたAPIの形でフロントエンドエンジニアに提供できます。

現在、バックエンドからのデータ管理を支援するReduxまわりのライブラリ（[redux-observable](https://github.com/redux-observable/redux-observable), [redux-saga](https://redux-saga.js.org/), [redux-thunk](https://github.com/reduxjs/redux-thunk)など）が無数にありますが、それらは既に重たいボイラープレートのライブラリに複雑な層を追加しています。私はこれらのほとんどが的外れだと思っています。時には一歩前進する前に、一歩後退する必要もあります。

フロントエンド側でバックエンドの状態を管理することをやめて、定期的に更新するだけのキャッシュのように扱うのはどうでしょうか？フロントエンドをキャッシュから読み込む単純なディスプレイレイヤーとして扱えば、コードが格段に簡潔になってよりフロントエンドエンジニアが取り扱いしやすくなります。SPAを構築するデメリットのほとんどを避けながら、関心事を分離するメリットを享受できます。

## バックエンドの状態へのシンプルなアプローチ
バックエンドの状態を保存するために、Redux（または類似の状態管理ライブラリ）を使うより大きな改善が見込めるライブラリがたくさんあります。

### React Query
私は数ヶ月前から個人的なプロジェクトと仕事のプロジェクトのほとんどで[React Query](https://github.com/tannerlinsley/react-query)を使っています。React Queryは非常にシンプルなAPIとクエリ（データの取得）とミューテーション（データの変更）を管理するフックがいくつかあるライブラリです。  
React Queryを使い始めてから生産性が上がっただけでなく、Reduxを使っていたときっよりも10倍少ない定型的なコードを書けました。バックエンド全体の状態を頭に入れていなくてもフロントエンドアプリケーションのUI/UXに集中しやすくなりました。

React QueryとReduxを比較するにはコードで見るとわかりやすいです。vanilla.jsと[React Hooks](https://reactjs.org/docs/hooks-intro.html), [axios](https://github.com/axios/axios)を使ってそれぞれの方法でサーバーからTODOリストを取得するコードを書きました。

まずはReduxを使った時の場合です。

```react

import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import axios from 'axios';

const SET_TODOS = "SET_TODOS";

export const rootReducer = (state = { todos: [] }, action) => {
  switch (action.type) {
    case SET_TODOS:
      return { ...state, todos: action.payload };
    default:
      return state;
  }
};

export const App = () => {
  const todos = useSelector((state) => state.todos);
  const dispatch = useDispatch();

  useEffect(() => {
    const fetchPosts = async () => {
      const { data } = await axios.get("/api/todos");
      dispatch({
        type: SET_TODOS,
        payload: data}
      );
    };

    fetchPosts();
  }, []);

  return (
    <ul>{todos.length > 0 && todos.map((todo) => <li>{todo.text}</li>)}</ul>
  );
};
```

データの再取得やキャッシュ、無効化処理はしていません。単にデータをロードして、ロード時にグローバルストアに保存しているだけです。

同じ内容をReact Queryを使って実装すると次のようになります。

```react
import React from "react";
import { useQuery } from "react-query";
import axios from "axios";

const fetchTodos = () => {
  const { data } = axios.get("/api/todos");
  return data;
};

const App = () => {
  const { data } = useQuery("todos", fetchTodos);

  return data ? (
    <ul>{data.length > 0 && data.map((todo) => <li>{todo.text}</li>)}</ul>
  ) : null;
};
```

この例では、[デフォルトで](https://react-query.tanstack.com/docs/guides/important-defaults)データの再取得、キャッシュ、古い状態の無効化が含まれています。キャッシュ設定はグローバルレベルで設定できますが、これは忘れても構いません。大体において期待通りに動作します。これがどのように動作するかについては[React Queryのドキュメント](https://react-query.tanstack.com/docs/overview)を確認してください。多くの設定オプションがあり、この例はそれらのほんの一部に過ぎません。

データが必要なところであればどこでも設定したキー（この例では"todos"）とデータ取得に使う非同期呼び出しをして[useQueryフック](https://react-query.tanstack.com/docs/guides/queries)を使えます。関数が非同期である限り、実装はそこまで難しくありません。axiosの代わりに[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)を使うのと同じような感じです。

バックエンドの状態を変更する用途にはReact Queryは[useMutationフック](https://react-query.tanstack.com/docs/guides/mutations)を用意しています。

React Queryのリソースをまとめたリストも書いたので、[ここ](https://github.com/Buuntu/awesome-react-query)から確認してみてください。

### SWR
[SWR](https://github.com/vercel/swr)はコンセプト的にはReact Queryとほぼ同じです。React QueryとSWRはほぼ同時期に開発され、お互いにいい影響を与えあっています。[React-Queryのドキュメント](https://react-query.tanstack.com/docs/comparison)に、これら2つのライブラリの比較もあります。

React Queryのように、SWRも本当に読みやすい[ドキュメント](https://swr.vercel.app/)が整備されています。ほとんどの場合、どちらのライブラリを使っても間違いはありません。近い将来どちらがスタンダードになるかどうかに関わらず、Reduxによる複雑さよりもこれらどちらかのライブラリを使った状態からリファクタリングするほうが遥かに簡単でしょう。

### Apollo Client
SWRとReact QueryはREST APIに焦点を当てていますが、GraphQL用に類似ライブラリが必要な場合、有望な候補は[Apollo Client](https://www.apollographql.com/docs/react/)です。構文がReact Queryとほぼ同じだとわかります。

## フロントエンド側の状態はどうするか？
これらのライブラリを使えば、多くのプロジェクトでReduxはやりすぎてあると気づくでしょう。アプリのデータ取得/キャッシュの部分を処理してしまえば、フロントエンド側で処理するグローバルな状態はほとんどありません。その僅かに残った状態は[Context](https://reactjs.org/docs/context.html)か[useContext](https://blog.bitsrc.io/build-our-own-react-redux-using-usereducer-and-usecontext-hooks-a5574b526475)＋[useReducer](https://blog.bitsrc.io/build-our-own-react-redux-using-usereducer-and-usecontext-hooks-a5574b526475)を使って擬似的なReduxで処理できるでしょう。

あるいは、シンプルな状態にはReactに備わっているuseStateを使うのが良いでしょう。この対応でも本質的に何も間違っていません。

```react
// clean, beautiful, and simple
const [state, setState] = useState();
```
バックエンドとフロントエンドが曖昧に分かれる状態ではなく、完全に分離された状態を受け入れましょう。これらの新しいライブラリはSPAにおける状態管理方法のシフトを表しており、正しい方向への大きな一歩です。これらライブラリがReactコミュニティをどう導くのか今から楽しみです。