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

【React】redux-toolkitをTypeScriptで使う時の個人メモ

Last updated at Posted at 2024-12-05

import

npm i @reduxjs/toolkit react-redux
  • sliceを作成
  • 作成したsliceからreduceractionをexport
  • configureStore()storeを作成し、reducerプロパティにreducerを渡す
  • useSelectoruseDispatchに対して、それぞれ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;
0
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
0
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?