0
0

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.

useStateからreact-reduxに置き換える

Last updated at Posted at 2022-02-23

前提

以前作成した下記記事のHooksを使って置き換えます

reduxとtoolkitの導入

yarn add react-redux @types/react-redux @reduxjs/toolkit

ファイルの変更と追加

index.tsx

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";

import App from "./app";
import { store } from "./redux/store";

ReactDOM.render(
<React.StrictMode>
    <Provider store={store}>
    <App />
    </Provider>
</React.StrictMode>,
document.getElementById("root")
);
  • <Provider>でAppを囲う

<Provider>:Reduxのstoreを利用できるようにする

src > reduxフォルダ > store.ts

import { configureStore, combineReducers } from "@reduxjs/toolkit";

export const store = configureStore({
reducer: combineReducers({}),
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
  • store:stateの保存場所
  • useStateではpropsでデータをバケツリレーしていたが、reduxではstoreから直接データを渡せる
  • 様々なサイトを見た時にconfigureStorecombineReducersのファイルを分けていることがあるが、最初はstore.tsにまとめておく(Reducerが多くなったら切り分ける可能性あり)

fetchの置き換え

app.tsx useState版

import React, { FC, useEffect, useState } from "react";
import { Content } from "./component/content";
import { Ranking } from "./types/type";

const App: FC = () => {
  const [ranking, setRanking] = useState<Ranking[]>([]);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch("http://localhost:3000/ranking");
      const rankingData = await response.json();
      setRanking(rankingData);
    };
    fetchData();
  }, []);
  return (
    <>
      <Content ranking={ranking} />
    </>
  );
};
export default App;

app.tsx redux置き換え版

import React, { FC, useEffect } from "react";
import { useDispatch } from "react-redux";

import { Content } from "./component/content";
import { ranking } from "./redux/fetch";

const App: FC = () => {
  const dispatch = useDispatch();
  useEffect(() => {
    dispatch(ranking());
  }, []);
  return (
    <>
      <Content />
    </>
  );
};
export default App;
  • useEffect内にあったfetchを切り分け
  • useStateが必要なし
  • Contentコンポーネントに渡していたデータも必要なし

fetchの切り分けは下記

redux > fetch.ts

import { createAsyncThunk } from "@reduxjs/toolkit";

export const ranking = createAsyncThunk("rankingData", async () => {
const response = await fetch("http://localhost:3000/ranking");
const rankingData = await response.json();
return rankingData;
});
  • fetchの際にasyncを使っていたので非同期処理であり、ReduxのデータフェッチとRedux toolkitに記載されていたので使用

createAsyncThunk:非同期処理に対応したActionCreatorを生成する関数

ここまでで確認①

  1. app.tsx<Content>は一旦、<p>TEST</p>に変更
  2. content.tsxのコードは全てコメントアウト
  3. yarn mockyarn startを実行
  4. http://localhost:8111でブラウザの検証からReduxを開く※google chromの拡張機能Redux DevTools

rankingData/fullfilledaction > payload内にデータが入っていればdispatchしているfetchは成功

Sliceを使ってStoreにデータを送る

redux >  rankingSlice.ts

import { createSlice } from "@reduxjs/toolkit";

import { RankingData } from "../types/type";
import { ranking } from "./fetch";

const initialState: RankingData = {
ranking: [],
};

export const rankingDataSlice = createSlice({
name: "rankingData",
initialState,
reducers: {},
extraReducers: (builder) => {
    builder
    .addCase(ranking.pending, () => {
        //非同期処理中のロジック
    })
    .addCase(ranking.fulfilled, (state, { payload }) => {
        //非同期処理成功時のロジック
        state.ranking = payload;
    })
    .addCase(ranking.rejected, (error) => {
        //非同期処理失敗時のロジック
        error;
    });
 },
});
  • sliceでデータの更新
  • 今回のuseStateの役割 = Slice
  1. useState版ranking = redux版initialState
  2. useState版setRanking = redux版extraReducers

extraReducers:「外部」アクションを参照することを目的としているため、アクションで生成されたものは使えない
reducers:特定のアクションタイプを処理するための関数で、switchのcase文に相当

store.ts

import { rankingDataSlice } from "./rankingSlice";
import { configureStore, combineReducers } from "@reduxjs/toolkit";

export const store = configureStore({
reducer: combineReducers({ ranking: rankingDataSlice.reducer }),
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
  • storeでデータ保管
  • rankingDataSliceをimport
  • reducerにSliceのデータを追加

ここまでで確認②

  1. yarn mockyarn startを実行
  2. ブラウザの検証からReduxを開く

rankingData/fullfilledstate > ranking > ranking内にデータが入っていればstoreにデータが入っているのでOK!
あとはstoreからデータを取り出すだけ!

Storeからデータを与える

今回Storeからデータを与えたいのは子であるcontent.tsx

content.tsx

import React, { FC } from "react";
import { useSelector } from "react-redux";
import { RootState } from "../redux/store";
import { RankingData } from "../types/type";

export const Content: FC<RankingData> = () => {
  const { ranking } = useSelector((state: RootState) => state.ranking);
  if (!ranking) throw new Error("rankingがありません");

  return (
    <>
      <h1>Hello Hello</h1>
      <table>
        <tr>
          {ranking.map(({ word }) => {
            return (
              <tr>
                <td key={word}>{word}</td>
              </tr>
            );
          })}
        </tr>
      </table>
    </>
  );
};
  1. コメントアウトを消す

  2. propsの部分も消す

  3. app.tsx<p>TEST</p><Content/>に変更

  4. <Content/>でエラーが出ているのでtype.d.tsを編集(rankingに?をつける)

     export interface RankingData {
       ranking?: Ranking[];
      }
    
  • reduxからuseSelectorをimport
  • stateを管理しているstoreからRootStateもimport
  • あとはuseSelectorrankingのデータを持ってくる
  • rankingを持ってきた時にundefinedの可能性があるのでタイプガードをする

最後に

yarn mockyarn startを実行
ここでmockのデータが表示されていたら成功!

今回はcomponent1つですが複数になった場合や孫のコンポーネントが出てきた場合、useStateであるとpropsのバケツリレーは大変です。
理解するのは難しいですがreduxとtoolkitを使えるようになるとコードがスッキリし、storeから直接データを渡せるので便利です!

参考文献

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?