【React】Redux Toolkitを使ってみた

仕事ではReact Reduxを使用しているのですが、今更ながらRedux Toolkitを試してみた際のメモです。

環境構築

npx create-react-app redux-toolkit-example --template redux

標準でCounterのサンプルがあるので、これを見ていきます。

createSlice

nameがsliceの識別名、initialStateが初期値です。
他は後述します。

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

const initialState = {
  value: 0,
  status: 'idle',
};

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(incrementAsync.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(incrementAsync.fulfilled, (state, action) => {
        state.status = 'idle';
        state.value += action.payload;
      });
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

export const selectCount = (state) => state.counter.value;

export const incrementIfOdd = (amount) => (dispatch, getState) => {
  const currentValue = selectCount(getState());
  if (currentValue % 2 === 1) {
    dispatch(incrementByAmount(amount));
  }
};

export default counterSlice.reducer;

reducers

ここにactionを記入します。
サンプルでは、
1. increment
2. decrement
3. incrementByAmount
の3つのアクションが定義されています。

reducers: {
  increment: (state) => {
    state.value += 1;
  },
  decrement: (state) => {
    state.value -= 1;
  },
  incrementByAmount: (state, action) => {
    state.value += action.payload;
  },
},

createAsyncThunk

React ReduxではRedux ThunkでReduxで非同期関数を扱っていましたが、Redux Toolkitでは createAsyncThunkを使用します。
createAsyncThunkの第1引数にアクション名、第2引数に非同期関数を渡します。

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

export const incrementAsync = createAsyncThunk(
  'counter/fetchCount',
  async (amount) => {
    const response = await fetchCount(amount);
    return response.data;
  }
);

extraReducers

非同期処理の後処理になります。
pendingが処理中、fulfilledが正常終了時の後処理になります。
サンプルにはありませんが、rejectedが失敗時になります。

extraReducers: (builder) => {
    builder
      .addCase(incrementAsync.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(incrementAsync.fulfilled, (state, action) => {
        state.status = 'idle';
        state.value += action.payload;
      });
  },

Store

Storeに先程作成したReducerを登録しています。

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';

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

useSelector

コンポーネント側でstoreのstateを取得します。

import { useSelector } from 'react-redux';
import {
  selectCount,
} from './counterSlice';

export function Counter() {
  const count = useSelector(selectCount);
}

useDispatch

dispatchの引数にaction(下記の例ではincrement)を渡す事で、イベント発火時にreducerのactionが実行されます。

import { useDispatch } from 'react-redux';

export function Counter() {
  const dispatch = useDispatch();
  return (
    <div>
        <button
          className={styles.button}
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
    </div>
  );
}

今回はサンプルを試してみただけですが、React Reduxより取っ付きやすくなっているなと感じました。

未分類

Posted by ababa