import
npm i @reduxjs/toolkit react-redux
-
slice
を作成 - 作成したsliceから
reducer
とaction
をexport -
configureStore()
でstore
を作成し、reducerプロパティにreducerを渡す -
useSelector
とuseDispatch
に対して、それぞれwithTypes<T>
メソッドで型定義する -
store
をProviderコンポーネントに渡す
Storeの作成
store.ts
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./slice/counterSlice";
import { useDispatch, useSelector } from "react-redux";
const store = configureStore({
reducer: {
counter: counterReducer,
},
});
// stateの型を全部取得する
export type RootState = ReturnType<typeof store.getState>;
// dispatchの型を全部取得する
export type RootDispatch = typeof store.dispatch;
// 型情報をもったuseSelectorとuseDispatchを作成
const useAppSelector = useSelector.withTypes<RootState>();
const useAppDispatch = useDispatch.withTypes<RootDispatch>();
export { store, useAppSelector, useAppDispatch };
Storeの利用
Index.tsx
import { store } from "./store/store";
import { Provider } from "react-redux";
import Counter from "./Counter";
const Index = () => {
return (
<Provider store={store}>
<Counter />
</Provider>
);
};
export default Index;
Slicer用のtypesを定義
types.ts
export interface Counter {
count: number;
status: string;
}
Slicerの作成
counterSlice.ts
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
import { asyncCount } from "../../api/counterApi";
import { type RootDispatch, type RootState } from "../store";
import { type Counter } from "../types";
const initialState: Counter = {
count: 0,
status: "",
};
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
add(state, { payload }: PayloadAction<number>) {
state.count = state.count + payload;
},
minus(state, { payload }: PayloadAction<number>) {
state.count = state.count - payload;
},
},
// 処理状況ごとに表示するステータスを更新
extraReducers: (builder) => {
builder
.addCase(addAdyncWithStatus.pending, (state) => {
state.status = "Loading...";
})
.addCase(addAdyncWithStatus.fulfilled, (state, { payload }) => {
state.status = "取得済";
state.count = state.count + payload;
})
.addCase(addAdyncWithStatus.rejected, (state) => {
state.status = "エラー";
});
},
});
// /_/_/_/_/_/_/_/_/_
// Redux-thunc
// /_/_/_/_/_/_/_/_/_
// ステータス無し
export const addAdync = (payload: number) => {
return async (dispatch: RootDispatch, getState: () => RootState) => {
const state = getState();
console.log(state);
const response = await asyncCount(payload);
dispatch(add(response.data));
};
};
// ステータスつき
export const addAdyncWithStatus = createAsyncThunk(
"counter/asyncCount",
async (payload: number) => {
const response = await asyncCount(payload);
return response.data;
}
);
export default counterSlice.reducer;
export const { add, minus } = counterSlice.actions;
redux-thunk用の非同期処理(なくてもOK)
CounterAPI
export const asyncCount = (count: number = 1) => {
return new Promise<{ data: number }>((resolve) => {
setTimeout(() => {
resolve({ data: count });
}, Math.random() * 1000);
});
};
dispathやselectorの利用
Counter.tsx
import { useAppSelector, useAppDispatch } from "./store/store";
import { add, minus, addAdync } from "./store/slice/counterSlice";
const Counter = () => {
const counter = useAppSelector((state) => state.counter);
const dispatch = useAppDispatch();
return (
<div className="w-screen h-screen">
<div className="w-full h-full flex flex-col justify-center items-center">
<div>{counter}</div>
<div className="w-full flex justify-center items-center">
<button onClick={() => dispatch(add(2))}>+2</button>
<button onClick={() => dispatch(minus(2))}>-2</button>
<button onClick={() => dispatch(addAdync(2))}>非同期(+2)</button>
</div>
</div>
</div>
);
};
export default Counter;
reducerが受け取る前の値を加工する処理
prepare
を使う
bookSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Book } from "@/type";
import { v4 as uuid } from "uuid";
const initialState: Book[] = [
{
id: "001",
title: "日本史",
},
];
const bookSlice = createSlice({
name: "book",
initialState,
reducers: {
bookAdd: {
// idはuuidで作成する
prepare: (data: Omit<Book, "id">) => {
return {
payload: { ...data, id: uuid() },
};
},
reducer: (state, { payload }: PayloadAction<Book>) => {
state.push(payload);
},
},
bookDelete: (state, { payload }: PayloadAction<Pick<Book, "id">>) => {
const removeIndex = state.findIndex((x) => x.id === payload.id);
// splice(対象の要素インデックス, 対象の要素インデックスから何個の要素を削除するか)
state.splice(removeIndex, 1);
},
},
});
export default bookSlice.reducer;
export const { bookAdd, bookDelete } = bookSlice.actions;