Redux Toolkit을 사용해 보자

Jaewook·2022년 8월 13일
0
post-thumbnail

Redux Toolkit 이란 뭘까?

RTK의 핵심은 리덕스의 복잡함을 낮추고 사용성을 개선하는 것입니다.
그럼 리덕스는 왜 복잡하다고 생각할까요?

  • 리덕스에는 보일러 플레이트가 너무 많습니다. 하나의 상태를 관리하기 위해서 액션 타입, 액션 생성 함수, 리듀서를 작성해야 하는 번거로움이 있습니다.
  • 리덕스의 초기 환경설정을 하는 과정이 복잡합니다.
  • 리덕스를 잘 사용하기 위해서 많은 패키 치를 설치해야 합니다.
  • 불변성을 지키기 위해 spread를 사용하거나 immer 등 다른 라이브러리를 사용해야 합니다.

RTK의 장점은 뭘까?

  • 기본적은 immer가 내장되어 있어 상태의 불변성을 지키는 것이 매우 편리하다.
  • 타입 스크립트를 지원하고 있다.
  • redux thunk가 내장되어 있어 비동기 작업이 수월하다
  • 보일러 플레이트가 필요 없이 내장 함수 하나만으로 액션 타입, 액션 생성 함수, 리듀서를 작성할 수 있다.

RTK 설치

npm install @reduxjs/toolkit //npm

yarn add @reduxjs/toolkit //yarn

configureStore

redux와 같이 store를 생성해 줍니다.
configureStore를 통해 reducer를 정의해 줍니다.
또한 store.getState를 통해서 state의 값을 가져옵니다.

import { configureStore } from '@reduxjs/toolkit'
// ...
const store = configureStore({
  reducer: {
    one: oneSlice.reducer,
    two: twoSlice.reducer,
  },
})

//state
export type RootState = ReturnType<typeof store.getState>
//dispatch
export const useAppDispatch: () => AppDispatch = useDispatch

export default store

createSlice

createSlice를 통해서 reducer를 간단하게 정의할 수 있습니다.
간단한 counter 예제를 통해 알아보겠습니다.

createSlice 안에는 name, initialState, reducers로 구성될 수 있습니다.
RTK의 장점에서 봤듯이 immer가 내장되어 있기 때문에 불변성을 신경 쓰지 않고 코드를 작성할 수 있습니다.

import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState } from './store';

//initialState의 타입
interface CounterState {
  count: number;
  countExtra: number;
}

const initialState: CounterState = {
  count: 0,
  countExtra: 0,
};

//createSlice
export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increase: (state) => {
      state.count += 1;
    },
    decrease: (state) => {
      state.count -= 1;
    },
    increaseBy: (state, action: PayloadAction<number>) => {
      state.count += action.payload;
    },
  },
});

export const { increase, decrease, increaseBy } = counterSlice.actions;
export default counterSlice.reducer;

CreateSelector

createSelector는 우리가 알고 있는 Reselect와 동일합니다.
먼저 살펴보기 전에 기존에 사용해왔던 useSelector의 문제점을 알아봅시다.

useSelector

useSelector는 store에서 필요한 state를 가져와서 사용하기 위해 사용하는 함수이다.

  • 리액트는 state가 변경되는 경우 컴포넌트가 re-rendering이 된다.
  • useSelector를 통해 매번 컴포넌트가 redering 될 때마다 객체를 만들게 되면 매번 dispatch, 또는 해당 컴포넌트에서 값이 바뀌지 않는 state 값도 다시 redering이 되는 문제점이 있다.

이렇기 때문에 createSelector는 현재의 state 값을 memoization을 하여 state의 값이 변하지 않는다면 이전에 저장되었던 값을 불러오는 방식으로 불필요한 re-rendering을 막을 수 있다.

import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState } from './store';

//initialState의 타입
interface CounterState {
  count: number;
  countExtra: number;
}

const initialState: CounterState = {
  count: 0,
  countExtra: 0,
};

//createSlice
export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increase: (state) => {
      state.count += 1;
    },
    decrease: (state) => {
      state.count -= 1;
    },
    increaseBy: (state, action: PayloadAction<number>) => {
      state.count += action.payload;
    },
  },
});


//createSelector
//count의 값만 참조하는 selector 반환
const countSelector = (state: RootState): number => {
  return state.counter.count | initialState.count;
};
//countExtra값만 참조하는 selector 반환
const countExtraSelector = (state: RootState): number => {
  return state.counter.countExtra | initialState.countExtra;
};

//위에 정의한 2개의 함수로 createSelector 생성
export const counterSelector = createSelector(
  countSelector,
  countExtraSelector,
  (count, countExtra) => ({
    count,
    countExtra,
  }),
);

export const { increase, decrease, increaseBy } = counterSlice.actions;
export default counterSlice.reducer;

정리

무엇보다도 코드의 양을 줄일 수 있다는 것이 굉장한 장점인 것 같다.
매번 액션 타입, 액션 생성 함수, 리듀스를 작성하는 것에 피로감을 느끼고 있었기 때문에,,

아직 모든 기능을 사용해 보지는 않았지만 다른 많은 기능이 있기 때문에 공식 문서를 통해 알아보는 것이 중요할 거 같다.

참조

https://redux-toolkit.js.org/

0개의 댓글