前提
以前作成した下記記事の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
から直接データを渡せる - 様々なサイトを見た時に
configureStore
とcombineReducers
のファイルを分けていることがあるが、最初は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を生成する関数
ここまでで確認①
-
app.tsx
の<Content>
は一旦、<p>TEST</p>
に変更 -
content.tsx
のコードは全てコメントアウト -
yarn mock
とyarn start
を実行 -
http://localhost:8111
でブラウザの検証からReduxを開く※google chromの拡張機能Redux DevTools
rankingData/fullfilled
のaction > 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
-
useState版
:ranking
=redux版
:initialState
-
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のデータを追加
ここまでで確認②
-
yarn mock
とyarn start
を実行 - ブラウザの検証からReduxを開く
rankingData/fullfilled
のstate > 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>
</>
);
};
-
コメントアウトを消す
-
props
の部分も消す -
app.tsx
の<p>TEST</p>
は<Content/>
に変更 -
<Content/>
でエラーが出ているのでtype.d.ts
を編集(rankingに?をつける)export interface RankingData { ranking?: Ranking[]; }
-
redux
からuseSelector
をimport -
state
を管理しているstore
からRootState
もimport - あとは
useSelector
でranking
のデータを持ってくる -
ranking
を持ってきた時にundefined
の可能性があるのでタイプガードをする
最後に
yarn mock
とyarn start
を実行
ここでmockのデータが表示されていたら成功!
今回はcomponent1つですが複数になった場合や孫のコンポーネントが出てきた場合、useStateであるとpropsのバケツリレーは大変です。
理解するのは難しいですがreduxとtoolkitを使えるようになるとコードがスッキリし、store
から直接データを渡せるので便利です!
参考文献