Redux-toolkit과 Redux의 차이점

Mayton·2023년 1월 5일
1

ComputerScience

목록 보기
5/7
post-thumbnail

웹 페이지의 최적화에대해서 알아보는 중 redux를 사용할 때 업데이트 되지 않아도 object 내에 다른 정보가 수정되면 지속해서 함수가 재실행되어 리렌더링이 되게 만들어지는 것을 방지하기 위해 reselect를 사용하는 방법이 있다는 것을 확인하였다. 그래서 이번 프로젝트간 사용 하려고 했던 redux-toolkit에서는 어떻게 적용을 시켜야하나 알아보니 이미 redux-toolkit에서는 createSeelector를 통해 리렌더링을 방지할 수 있었다. 이처럼 여러 기능과 라이브러리들을 redux-toolkit을 통해 사용할 수 있었는데, 어떤 부분들이 있는지 구체적으로 알아보자

🙋🏻‍♂️ Redux-toolkit이 만들어진 이유

redux-toolkit 공식사이트를 참고하면 redux-toolkit이 만들어진 이유가 제일 상단에 나와있다.

  1. redux 스토어 구성의 복잡성
  2. redux를 사용할 때 많은 패키지 추가의 필요성
  3. redux 사용시 많은 상용 코드 필요

간단하게 이야기하면 지속해서 제시되었던 redux가 대규모 프로젝트들에서 편리하기는 하지만, 배보다 배꼽이 더 크다할 정도로 redux를 사용하게되면 코드 작성양이 늘어나고 필요로하는 라이브러리들이 굉장히 많다는 단점을 보완하기 위해서 출시되었다고 할 수 있다.

🙋🏻‍♂️ Redux-toolkit에서 지원하는 기능

👉🏻 redux-action

const increment = createAction("INCREMENT");
const decrement = createAction("DECREMENT");

function counter(state = 0, action) {
  switch (action.type) {
    case increment.type:
      return state + 1;
    case decrement.type:
      return state - 1;
    default:
      return state;
  }
}

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

document.getElementById("increment").addEventListener("click", () => {
  store.dispatch(increment());
});

위와 같이 createAction을 통해 액션 타입을 정의할 수 있지만 createSlice를 사용하면 createAction의 사용없이 자동으로 action 타입을 정의해 준다.

interface CartState {
  products?: Product[];
}

const initialState: CartState = {
  products: [],
};

export const cartSlice = createSlice({
  name: 'cart',
  initialState,
  reducers: {
    initializeCart: () => {},
    addCart: () => {},
    removeCart: () => {},
  },
});

export const { initializeCart, addCart, removeCart } = cartSlice.actions;

export default cartSlice.reducer;

👉🏻 reselect

reselect의 장점

redux는 store에서 값을 가져와 객체의 값 중 일부만 가져올 때, 리렌더링을 일으키지 않도록 도와준다.

필요로하는 state를 첫번째 인자로 넣고, 그 state에서 어떤 값을 필요로 하는지 필터링 해줄 콜백함수를 넣어준다.
기존에는 reference만을 비교하여, 값이 바뀌지 않고 참조하는 object의 reference 값만 바뀌어도 함수가 다시 실행되고, 리렌더링이 되었지만 createSelector를 통해 값을 가져올 시에는 그 값이 캐싱되어 그 값이 바뀌었는 지 여부를 확인하게 되어 최적화를 시킬 수 있다.

const productsState = (state: RootState) => state.products.products;

export const getElectronicsProducts = createSelector(
  productsState,
  (products) => {
    return products.filter((product) =>
      product.category.==='electronics'
    );
  }
);

👉🏻 immer의 produce

redux에서도 react에서와 마찬가지로 reference 값 비교(얕은 비교)를 통해 상태를 확인한다. 그래서 redux에서는 상태를 직접 변경해서는 안되고 상태를 직접변경하게되면 아래와 같은 문제점들을 야기한다.

  • UI가 최신값으로 제대로 업데이트 되지 않는다
  • 상태가 업데이트 된 이유와 방법을 이해하기 어렵다.
  • 테스트 작성이 어렵다.
  • 시간디버깅을 올바르게 사용하는 기능이 중단된다.

redux-toolkit 에서 createReducer API는 내부적으로 자동으로 immer를 사용한다. 그래서 아래와 같이 직접 값을 push하는 것도 가능하다.

const todosReducer = createReducer([], (builder) => {
  builder.addCase('todos/todoAdded', (state, action) => {
    // "mutate" the array by calling push()
    state.push(action.payload)
  })
})

이때 우리는 redux-toolkit을 사용하게 되면 createReducer를 직접 쓸 상황은 없을 텐데 createSlice 내의 reducers 안에서 createReducer를 내부적으로 사용하기 때문이다.

export const cartSlice = createSlice({
  name: 'cart',
  initialState,
  reducers: {
    initializeCart: () => {},
    addCart: () => {},
    removeCart: () => {},
  },
});

주의할점

  • immer에서는 기존 draft 상태 값을 변경하려는 시도를 추적하지만, 기존상태를 변경했음에도 불구하고 상 상태값을 직접 반환하면 오류가 발생한다.
const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    // ❌ ERROR: mutates state, but also returns new array size!
    brokenReducer: (state, action) => state.push(action.payload),
    // ✅ SAFE: the `void` keyword prevents a return value
    fixedReducer1: (state, action) => void state.push(action.payload),
    // ✅ SAFE: curly braces make this a function body and no return
    fixedReducer2: (state, action) => {
      state.push(action.payload)
    },
  },
})
  • 전체 state를 직접 재할당하는 경우 오류 발생

일반적인 실수는 state = someValue를 직접 할당하는 경우 지역 상태 변수는 다른 참조로 가리킨다. 이는 메모리의 기존 상태 개체/배열을 변경하거나 완전히 새로운 값을 반환하지 않으므로 Immer는 실제 변경을 수행하지 않는다.

👉🏻 Flux Standard Action 강제화

Flux 디자인 패턴을 사용하지 않으면 무조건 적으로 에러를 띄우며 대표적인 예로 값은 action.payload를 통해서만 접근해야한다.

export interface Action<Payload> extends AnyAction {
  type: string;
  payload: Payload;
  error?: boolean;
  meta?: Meta;
}

👉🏻 Type Definition

아래와 같이 type에 관한 정의 들을 내장 타입으로 지원하여 type에 대해 신경써야하는 번거로움이 사라졌다.


export const store = configureStore({
  reducer: {
    user: userSlice,
    products: productSlice,
    cart: cartSlice,
  },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

추가

추가적으로 redux-thunk를 따로 설치해서 사용했던것을 공식적으로 지원한다.createAsyncThunk를 이용해서 그 전의 thunk와 같이 사용할 수 있다. 다른 비동기 라이브러리들이 존재하지만 thunk를 공식적으로 지원하게 된 이유는 공식 홈페이지에서 자세하게 찾아볼 수 있었다.

참고

redux-toolkit
redux-toolkit을 소개합니다.
redux-toolkit을 사용하여 간단하게 상태관리하기
Redux Toolkit: Immer와 함께 사용하기
js참조에 대한 시각적 가이드
introduction to immer

profile
개발 취준생

0개의 댓글