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

## import
```terminal
npm i @reduxjs/toolkit react-redux
```

- `slice`を作成
- 作成したsliceから`reducer`と`action`をexport
- `configureStore()`で`store`を作成し、reducerプロパティにreducerを渡す
- `useSelector`と`useDispatch`に対して、それぞれ`withTypes<T>`メソッドで型定義する
- `store`をProviderコンポーネントに渡す


## Storeの作成
```ts: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の利用
```tsx: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を定義
```ts:types.ts
export interface Counter {
  count: number;
  status: string;
}
```

## Slicerの作成
```ts: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）
```ts:CounterAPI
export const asyncCount = (count: number = 1) => {
  return new Promise<{ data: number }>((resolve) => {
    setTimeout(() => {
      resolve({ data: count });
    }, Math.random() * 1000);
  });
};

```

## dispathやselectorの利用
```tsx: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`を使う
```react: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;
```