본문 바로가기
FE/React

Redux 불변성을 지키면서 상태 변화를 손쉽게 하는 방법, immer

by Jiyoon-park 2021. 2. 15.

불변성, 불변성! 리덕스를 사용하면서 귀에 딱지 앉게 들었을 불변성 🙉 상태는 절대 절대 직접 변경하면 안된다. 특히나 리덕스는 객체의 상태 변화를 얕은 수준에서만 인식할 수 있기 때문에 객체의 깊은 상태를 변경하면 리덕스가 상태 변화를 못 알아차릴 수도 있다.

예를 들어 아래와 같이 깊은 객체가 있다고 한다면, 우리는 이 객체를 변경하기 위해

state = {
  병원: {
    소아병동: {
      207호: {
          환자1: {
              성별: 남,
              퇴원예정일: 3일 후 // 이 정보를 오늘로 바꾸어 보자!
            },
            ...
        },
        ...
    }
  }
}

아래처럼 새로운 객체를 생성하여 깊고 깊게 파고 들어가 변경을 해야한다.

const { 병원 } = this.state;
this.setState({
  병원: {
    ...병원,
    소아병동: {
      ...병원.소아병동,
      207호: {
          ...병원.소아병동.207호,
          환자1: {
              ...병원.소아병동.207호.환자1,
              퇴원예정일: 오늘🌈
            }
        }
    }
  }
})

번거롭당. 아주. 이런 번거로움을 해결해주는 library 가 있다.

 

immer

immer 를 통해 상태 불변성을 유지하며 손쉽게 상태 변경이 가능하다.

$ yarn add immer
// 기존
(...)
export default function counter(state=initialState, action) {
  switch(action.type) {
    case CHANGE_COLOR:
      return {
        ...state,
        color: action.color,
      }
    case INCREMENT:
      return {
        ...state,
        number: state.number + 1,
      }
    case DECREMENT:
      return {
        ...state,
        number: state.number - 1,
      };
    default:
      return state;
  }
}
// immer 사용
import produce from 'immer';

(...)
export default function counter(state=initialState, action) {
  switch(action.type) {
    case CHANGE_COLOR:
      return produce(state, draft => {
        draft.color = action.color
      })
    case INCREMENT:
      return produce(state, draft => {
        draft.number++;
      })
    case DECREMENT:
      return produce(state, draft => {
        draft.number--;
      })
    default:
      return state;
  }
}

얕은 객체에 있어서는 immer의 편리성을 크게 실감할 수 없지만, 깊은 객체일수록 immer의 편리성과 고마움은 커진다.

// 기존
export default handleActions(
  {
  [CHANGE_INPUT] : (state, action) => ({
    ...state,
    input:action.payload
  }),
  [CREATE] : (state,action) => ({
    ...state,
    list: state.list.concat({
      id: action.payload.id,
      name: action.payload.text,
      entered: false,
    })
  }),
  [ENTER] : (state, action) => ({
    ...state,
    list: state.list.map((user) => user.id === action.payload ? {...user, entered: !user.entered }: user )
  }),
  [LEAVE] : (state, action) => ({
    ...state,
    list: state.list.filter((user) => user.id !== action.payload)
  })
}, initialState);
// immer 을 사용한 reducer 생성
export default handleActions(
  {
  [CHANGE_INPUT] : (state, action) => produce(state, draft => {
    draft.input = action.payload;
  }),
  [CREATE] : (state,action) => produce(state, draft => {
    draft.list.push({
      id: action.payload.id,
      name: action.payload.text,
      entered: false,
    })
  }),
  [ENTER] : (state, action) => produce(state, draft => {
    const item = draft.list.find(item => item.id === action.payload);
    item.entered = !item.entered;
  }),
  [LEAVE] : (state, action) => produce(state, draft => {
    draft.list.splice(
      draft.list.findIndex(item => item.id === action.payload), 1
    )
  })
}, initialState);

 

 

공부 자료 ✍️ / velog.io/@velopert/Redux-3-리덕스를-리액트와-함께-사용하기-nvjltahf5e