1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

はじめに

フックAPIリファレンスuseReducerの存在は知っていましたが、実際に使ったことがなかったので、使い所を調べて使ってみた話をします。

useReducerとは

useStateの代用品

役割は、useStateと大体おんなじ感じってことですかね。

現在の state を dispatch メソッドとペアにして返します(もし Redux に馴染みがあれば、これがどう動作するのかはご存じでしょう)。
useReducer が useState より好ましいのは、複数の値にまたがる複雑な state ロジックがある場合
複数階層にまたがって更新を発生させるようなコンポーネントではパフォーマンスの最適化にもなります。

useStateまみれになったりするの嫌だなぁと思ったことあります。
それを1つのオブジェクトというかReduxStoreのように、管理できるような感じってことですかね。

Todoアプリのサンプルを作成

Todoアプリのサンプルを作成しました。

ファイル構成はこんな感じです。

  • src/components/pages/Todos/index.tsx
    • Todoの追加フォーム、リストデータの表示
  • src/stores/TodosStore/index.ts
    • TodosコンポーネントからuseStoreを呼び出している。
    • useStateを使って、loadingtodosを管理
    • Todoの追加用と削除用のメソッドを提供

画面の動きはこんな感じです。

バックエンドは用意していないので、それっぽく見せるために、Todoの追加時、削除時に、ダミーのfetchを使って、3秒間、ロードが発生するようにしています。

sample todo app.gif

Todo追加時の状態変更の流れは以下の3ステップです。

  1. loadingtrueに更新
  2. todosに1件追加更新
  3. loadingfalseに更新

頭の中のイメージ的には、ステップが1つ少ないです。

  1. loadingtrueに更新
  2. todosに1件追加とloadingfalseに更新

今回は、stateが2つなので、それほど複雑ではありませんが、useReducerを使って、ReduxStoreのように1つのオブジェクトのように管理してみます。

useReducerでリファクタリング

useStateを使っているところを、useReducerに変更

src/stores/TodosStore/index.ts
-const [loading, setLoading] = useState(initialState.loading);
-const [todos, setTodos] = useState(initialState.todos);
+const [state, dispatch] = useReducer(reducer, initialState);

reducerはこんな感じで定義しておきます。

reducer
const reducer = (state: TodosStore.State, action: TodosStore.Action) => {
  switch (action.type) {
    case "fetch":
      return fetchAction(state);
    case "add":
      return addAction(state, action);
    case "del":
      return delAction(state, action);
    default:
      return { ...initialState };
  }
};

useReducerで受けったdispatchは、dispatch({ type: "fetch" });という感じで呼び出すと、reducer関数の"fetch"caseが実行されるイメージです。

reducerは、元のstateを各処理によって、変更を加えた新しいstateを返すように記述します。

各処理はこんな感じで定義しておきます。

fetchAction
const fetchAction = (state: TodosStore.State): TodosStore.State => {
  return { ...state, loading: true };
};
addAction
const addAction = (
  state: TodosStore.State,
  { data: { name } }: TodosStore.AddAction
): TodosStore.State => {
  return {
    ...state,
    loading: false,
    todos: [
      ...state.todos,
      {
        id: new Date().getTime(),
        name,
      },
    ],
  };
};
delAction
const delAction = (
  state: TodosStore.State,
  { data: { id } }: TodosStore.DelAction
): TodosStore.State => {
  const todos = state.todos.filter((todo) => todo.id !== id);

  return {
    ...state,
    loading: false,
    todos,
  };
};

Todosコンポーネント側から呼び出している、Todo追加と削除の関数内で、dispatchを使ったstateの更新方法に修正します。

src/stores/TodosStore/index.ts
+  const dispatchAddTodo = (name: string) => {
+    dispatch({ type: "fetch" });
+
+    return dummyFetch().then(() => dispatch({ type: "add", data: { name } }));
+  };
+
+  const dispatchDelTodo = (id: number) => {
+    dispatch({ type: "fetch" });
+
+    return dummyFetch().then(() => dispatch({ type: "del", data: { id } }));
+  };

   const store: TodosStore.Store = {
     state,
     dispatchAddTodo,
     dispatchDelTodo,
   };

   return store;
 }

動きに変化はありませんが、頭の中のイメージ通りのステップ数になったので、レンダリング回数が減りました。

というわけで、リファクタリング完了!

おわりに

ベストプラクティスは分かっていませんが、自分なりにReduxを意識して、StoreReducerAction用にファイルを分けて、簡潔に実装できたのではないかと思います、

今回は、1ファイルに全ての機能を実装していますが、実際は、ファイルが分かれたりして、でも同じstateを参照した状態でアクションを実行したいことがあると思います。

その時は、useContextを使えば、各ファイルから同じstateを参照できるような仕組みにもできると思います。(propsで頑張って渡す方法でも...)

stateに直接変更を加えるわけではなく、Store側でアクションを定義して、そちら側でstateを変更する仕組みの方が、安全性があると思いますし、何より、たくさん管理したい状態がある程、1箇所にまとまっていて、そこにアクションを追加していく方が、分かりやすいのかなと思いました。

参考文献

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?