Redux - toolkit

한대희·2023년 5월 26일
0

Redux

목록 보기
3/3
post-thumbnail

redux-toolkit의 구조

  • 먼저 react toolkit은 slice라는 개념이 존재 한다.
  • slice는 작은 store라고 생각하면 된다.
  • createSlice 를 통해 slice를 만들 수 있다.
  • 이러한 Slice들을 한 곳에 모아 큰 store를 구성하게 되는데 이것을 가능 하게 하는 것이 configureStore 다.
  • 이렇게 큰 store안에 여러 slice들이 있을 것이고, 각 slice들은 각각의 state를 가지고 있을 것이다.
  • 따라서 큰 스토어에 속해 있는 slice 중에 원하는 slice의 state를 가져와 사용하면 된다.

예제

  • 예제를 통해 toolkit의 사용법을 알아 보자.
  • 먼저 createSlice를 통해 작은 store인 slice를 하나 만들어 준다.
  • slice를 만들 때 객체를 전달하는데 객체 안에는 이름, 초기값, reducers를 전달 한다.
  • slice는 작은 store이기 때문에 store안에 state인 초기값과, 상태 변경 함수인 reducer가 있구나 라고 이해 하면 된다.


const counterSlice = createSlice({
  name: 'counter',
  initialState: {value: 0},
  reducers: {
    increase: (state, action) => {
      state.value = state.value + action.payload
    }
  }
})

  • 그 다음 configureStore를 통해 큰 store를 만들어 준다.
  • redux에서 store를 만들 때 reducer함수를 함께 전달 했던 것을 기억 할 것이다.
  • 따라서 여러 slice를 하나의 store로 만들어 주는 것이므로 각 slice의 reducer를 아래와 같이 함께 전달해 주면 된다.
const store = configureStore({
  reducer: {
    counter: counterSlice.reducer
  }
})
  • 여기 까지 하면 큰 store까지 생성을 했으므로 provider에 store를 전달을 하고, useSelector로 slice의 state값을 사용하면 된다.
const counterSlice = createSlice({
  name: 'counter',
  initialState: {value: 0},
  reducers: {
    increase: (state, action) => {
      state.value = state.value + action.payload
    }
  }
})

const store = configureStore({
  reducer: {
    counter: counterSlice.reducer
  }
})

export default function App() {

  return (
    <div id='container'>
      <h1>Root</h1>
      <Provider store={store}>
        <A />
      </Provider>
   </div>
  );
}


function A() {
  return (
    <div>
      <h1>
        A : 
      </h1>
      <B />
    </div>
  )
}

function B() {
// 😀 여기서 state인 큰 store의 state 즉, 모든 slice의 state를 의미하므로
// 😀 우리는 counter라는 slice의 state의 값을 사용할 것이고, 초기값을 value
// 😀 라고 지정을 했기 때문에 state.counter.value로 해당 값을 가져 오면 된다.

  const number = useSelector((state) => state.counter.value)
  const dispatch = useDispatch()
  return (
    <div>
      <h1>
        B : {number}
      </h1>
      <button onClick={() => {
// 😀 dispatch를 할때는 toolkit에서 제공하는 actions를 사용하면 따로 
// 😀 타입을 지정하지 않고 아래 처럼 counterSlice의 actions중 increase타입
// 😀 의 괄호 안에 값을 전달하면 해당 값이 action의 payload로 전달이 된다.
        dispatch(counterSlice.actions.increase(2))
      }}>숫자 증가</button>
    </div>
  )
}

공식 사이트에서 제공하는 예제

  • 터미널에 아래와 같이 입력하면 리덕스 툴 킷 공식 사이트에서 만들어 둔 리덕스 툴 킷을 사용하여 만들어진 어플리케이션에 대한 코드와 기능을 확인해 볼 수 있다.
npx create-react-app my-app --template redux-typescript

설치를 했으면 코드를 살펴 보자

  • app 폴더 안에 store파일이 있다. features 폴더안에 counterSlice와 ui를 보여주는 컴포넌트 등이 있다.

  • store 생성 할 때 reducer함수를 넣어 줘야 하기 때문에 먼저 reducer함수를 살펴보자.

  • toolkit에서는 slice라는 작은 store가 있는데 여기에 reducer를 만들어 줘야 한다. 따라서 CounterSlice파일의 코드를 살펴 보자.

import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState, AppThunk } from '../../app/store';
import { fetchCount } from './counterAPI';

// 😀 초기 state의 타입 지정
export interface CounterState {
  value: number;
  status: 'idle' | 'loading' | 'failed';
}

// 😀 slice에 전달할 초기 state 지정
// 🔥 여기서 초기값은 반드시 initialState라는 이름으로 만들어야 한다. 그렇지 않으면 에러 발생
const initialState: CounterState = {
  value: 0,
  status: 'idle',
};

// 😀 createAsyncThunk를 통해 비동기 작업을 만들어 주는 action을 생성한다.
// 먼저 action의 타입을 적어주고, action이 실행되었을 때 처리될 함수를 입력한다.
export const incrementAsync = createAsyncThunk(
  'counter/fetchCount',
  async (amount: number) => {
    const response = await fetchCount(amount);
    return response.data;
  }
);

// 😀 createSlice를 통해 slice를 만들어 주고 여기에 name, 초기 state, reducer를 입력한다.
// 😀 reducers에는 reducer함수에 전달 할 action의 타입을 지정해 둔다.
export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
  // 😀 action의 타입이 increment일때 state를 + 1 
    increment: (state) => {
      state.value += 1;
    },
  // 😀 action의 타입이 decrement일때 state를 - 1
    decrement: (state) => {
      state.value -= 1;
    },
  // 😀 action의 타입이 incrementByAmount일때 payload에 전달된 값을 +
  // 😀 여기서 PayloadAction은 payload의 타입을 지정할 수 있는 제네릭 이다.
  // 😀 incrementByAmount의 경우만 전달 받은 payload값을 이용하여 state를 변경시키기 때문에
  // 😀 여기서는 payload값을 사용하기 위해 action을 인자로 받는 것
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload;
    },
  },
// 😀 extraReducers는 다른 곳에서 정의된 액션생성함수를 사용하고 싶을 때 사용한다, 
// 가장 흔한 케이스는 비동기를 위해 createAsyncThunk 를 사용하여 정의된 액션함수를 사용하거나, 
// 다른 slice 에서 정의된 액션함수를 사용하는 경우이다.
// 아래의 extraReducers안에 있는 incrementAsync는 counterSlice 외부에 있는 액션 함수다.
// 비동기 함수가 pending일때, fulfilled일때, rejected일때 각각의 reducer를 정의

  extraReducers: (builder) => {
    builder
// 😀 비동기 함수가 pending상태 일때는 status를 loading으로 바꿔주고 
      .addCase(incrementAsync.pending, (state) => {
        state.status = 'loading';
      })
// 😀 비동기 작업이 완료가 되면 status를 idle로 바꾸고, 비동기 함수에서 리턴하는
// 값이 action.payload로 들어 가고, 그 다음value에 payload값을 +
      .addCase(incrementAsync.fulfilled, (state, action) => {
        state.status = 'idle';
        state.value += action.payload;
      })
// 😀 비동기 작업이 정상적으로 수행되지 못하면 status를 failed로 바꿔 준다.
      .addCase(incrementAsync.rejected, (state) => {
        state.status = 'failed';
      });
  },
});

// 😀 counterSlice의 reducers에 정의한 action의 타입들을 export
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// 😀 useSelector를 이용하여 state를 가져올때 인자로 넣어줄 함수
export const selectCount = (state: RootState) => state.counter.value;

// We can also write thunks by hand, which may contain both sync and async logic.
// Here's an example of conditionally dispatching actions based on current state.
export const incrementIfOdd =
  (amount: number): AppThunk =>
  (dispatch, getState) => {
    const currentValue = selectCount(getState());
    if (currentValue % 2 === 1) {
      dispatch(incrementByAmount(amount));
    }
  };

export default counterSlice.reducer;
  • 이제 app 폴더 안에 store파일을 살펴 보자.
  • 리덕트 툴킷 에서는 store만들 때 configureStore라는 api를 사용한다.
import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';

// 😀 스토어를 만들고 counterSlice에서 만든 reducer를 전달
export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;
profile
개발 블로그

0개의 댓글