본문으로 바로가기

immer로 redux state 불변성 유지하기

category State Management/⚛ Redux 2021. 5. 21. 16:36

무지성스럽게 불변성 관리를 할 수 있다길래 배웠다.

 

솔직히 지금까지 한 프로젝트에서 immer라 필요한 정도로 depth가 깊게 들어간 객체를 불변성 유지할 일이 없어서 사용을 안해왔다.

그 정도로 복잡해지기 전에 어떻게든 객체를 쪼개놓아야 한다는 생각이었다. 

그런데 아무래도 다음 프로젝트에서는 사용해야 할 것 같아서 찾아보기로. ㅎ

 

아래는 velopert가 reducer에 immer를 붙인 꼴이다.  아래와 같은 immer의 produce 함수를 활용하면 된다.

produce(currentState, producer: (draftState) => void): nextState

 

일반 local state에 사용해도 되고, redux에서 사용해도 된다.

 

local state에서 사용할 사람들은 아래 글을 읽어보자.

 

- react에서는 알아보니 use-immer라는 녀석이 표준처럼 쓰이고 있더라. immerjs 공식 패키지니 이용 안 할 이유가 없을 듯.

https://github.com/immerjs/use-immer

- custom 훅으로 만들어서 사용하는 예는 아래 참고.

https://css-tricks.com/using-immer-for-react-state-management/

 

 

사용법은 동일하니 redux의 reducer에서 사용하는 것으로 코드를 살펴보자.

 

이 코드는 기존, 불변성 관리를 위한 툴 없이 얕은 복사로 처리하던 코드다.

const postReducer = (prevState = initialState, action) => {
  switch (action.type) {
    case ADD_POST:
      return [...prevState, action.data];
    default:
      return prevState;
  }
};

 

이 코드를 아래와 같이 immer의 produce를 활용하여, 일반 객체를 수정하듯 관리할 수 있다. 

const postReducer = (prevState = initialState, action) => {
  switch (action.type) {
    case ADD_POST:
      return produce(prevState, (draft) => {
        draft.push(action.data);
      });
    default:
      return prevState;
  }
};

 

이런식으로 작성해도 되지만, 아래와 같이 case가 많아질 경우 produce를 여러번 써주는 것이 귀찮을 수도 있다. 

그러면 아래처럼 작성하는 경우도 있는데, 개인적으로는 위의 방법이 더 직관적인 것 같다.

이상하게 아래 같은 꼴로 사용하면 store에서 initialValue가 undefined이라는 둥 (분명 초기값을 넣어줬음에도) 사용하면서 불평이 많다.

const postReducer = (prevState = initialState, action) => {
  produce(prevState, (draft) => {
    switch (action.type) {
      case ADD_POST:
        draft.push(action.data);
      default:
        break;
    }
  });
};

 

 

* trouble shootings

 

1. Uncaught Error: [Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.

새 값을 반환하던가, draft를 수정하던가 둘 중 하나만 하라는 거라는 말이다.

에러 문구에 나와있지는 않지만, 쉽게 말하자면 정의 상에는 void를 반환하도록 되어 있으니까 그걸 좀 따라주자.

이게 다 타입스크립트를 안 써서 발생한 문제다. 

// Wrong
return produce(prevState, (draft) => draft.push(action.data));

// Good
return produce(prevState, (draft) => {
  draft.push(action.data);
});

 

2. commonjs랑 ESModule 방식이 좀 다르더라. 패키지 정의 부분 살펴보면 방식에 대한 차이를 이해할 수 있다.

const { produce } = require("immer");
import produce from "immer"

 

 

그 외 예시들)

 

1. velopert의 모던 리액트에서 나와있던 코드. 

function reducer(state, action) {
  switch (action.type) {
    case 'CREATE_USER':
      return produce(state, draft => {
        draft.users.push(action.user);
      });
    case 'TOGGLE_USER':
      return produce(state, draft => {
        const user = draft.users.find(user => user.id === action.id);
        user.active = !user.active;
      });
    case 'REMOVE_USER':
      return produce(state, draft => {
        const index = draft.users.findIndex(user => user.id === action.id);
        draft.users.splice(index, 1);
      });
    default:
      return state;
  }
}

 

 

 

ref)

https://github.com/immerjs/immer

https://react.vlpt.us/basic/23-immer.html


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체