리덕스 툴킷을 사용해보자.

 

 

store.js

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

const user = createSlice({
  name: 'user',
  initialState: 'kim',
  reducers: {
    changeName(state) {
      return `john ${state}`;
    },
  },
});

export const { changeName } = user.actions;

const cartItem = createSlice({
  name: 'cartItem',
  initialState: [
    { id: 0, name: 'White and Black', count: 2 },
    { id: 2, name: 'Grey Yordan', count: 1 },
  ],
});

export default configureStore({
  reducer: {
    user: user.reducer,
    cartItem: cartItem.reducer,
  },
});

 

위의 예제와 같이 간단하게 스토어와 슬라이스를 설정해두고, 이를 해당 컴포넌트에서 불러오기 위해

const stateGroup = useSelector((state) => state);

 

위와 같이 작성해보면, 아래와 같은 경고가 발생한다.

Selector unknown returned the root state when called. This can lead to unnecessary rerenders.
Selectors that return the entire state are almost certainly a mistake, as they will cause a rerender whenever anything in state changes.
{stack: 'Error\n    at http://localhost:3000/static/js/bundl…tp://localhost:3000/static/js/bundle.js:33570:22)'}
Cart	@	Cart.jsx:6
Show 20 more frames

 

위의 오류 메시지는 useSelector 훅이 전체 상태를 반환하는 경우 발생할 수 있는 문제를 경고하고 있다.

이렇게 하면 상태의 일부가 변경될 때마다 컴포넌트가 다시 렌더링되기 때문에 비효율적이라고 말한다. 

 

이를 해결하기 위해 combineReducers를 생각해보았는데, 이미 configureStore 내부에서 자동으로 처리되기 때문에 별도로 할 필요는 없다고 한다. 

 

const cartItem = useSelector((state) => state.cartItem);
const user = useSelector((state) => state.user);

 

이렇게 필요한 상태만 선택하여 사용해야 하나...?

 

여러 상태를 한 번에 가져오고 싶다면, useSelector 훅을 여러 번 호출하는 대신, 한 번의 호출로 필요한 상태들을 묶어서 가져오는 방식이 무엇일까?

 

구조 분해를 사용하여 가져와야 하나...

  const { cartItem, user } = useSelector((state) => ({
    cartItem: state.cartItem,
    user: state.user,
  }));

 

위와 같이 작성할 경우 아래와 같은 에러를 볼 수 있다.

Selector unknown returned a different result when called with the same parameters. This can lead to unnecessary rerenders.Selectors that return a new reference (such as an object or an array) should be memoized: https://redux.js.org/usage/deriving-data-selectors#optimizing-selectors-with-memoization {state: {…}, selected: {…}, selected2: {…}, stack: 'Error\n    at http://localhost:3000/static/js/bundl…tp://localhost:3000/static/js/bundle.js:33576:22)'}     at Cart (http://localhost:3000/main.5a25427396412b3edf33.hot-update.js:32:63)    at RenderedRoute (http://localhost:3000/static/js/bundle.js:50711:5)    at Outlet (http://localhost:3000/static/js/bundle.js:51315:26)    at Root    at RenderedRoute (http://localhost:3000/static/js/bundle.js:50711:5)    at RenderErrorBoundary (http://localhost:3000/static/js/bundle.js:50658:5)    at DataRoutes (http://localhost:3000/static/js/bundle.js:49278:5)    at Router (http://localhost:3000/static/js/bundle.js:51336:15)    at RouterProvider (http://localhost:3000/static/js/bundle.js:49065:5)    at Provider (http://localhost:3000/static/js/bundle.js:46149:3)

 

에러 메시지를 보면, useSelector 훅이 반환하는 객체가 매 렌더링마다 새로운 참조를 가지기 때문에 발생하는 문제로 보인다. 이를 해결하기 위해 알아보면 reselect 라이브러리를 사용해 셀렉터를 메모이제이션하는 방법을 고려해볼 수 있다.

 

npm install reselect
import { createSelector } from 'reselect';

const selectCartItem = (state) => state.cartItem;
const selectUser = (state) => state.user;

export const selectCartAndUser = createSelector(
  [selectCartItem, selectUser],
  (cartItem, user) => ({
    cartItem,
    user,
  })
);
const { cartItem, user } = useSelector(selectCartAndUser);