본문 바로가기
개발이야기/React

Immer

by dev.josh 2026. 2. 11.
반응형

Immer는 React에서 상태(state)를 업데이트할 때
마치 state.user.name = ... 처럼 직접 수정(mutate)하는 코드처럼 작성해도,
라이브러리 내부에서 안전한 불변성(immutability) 을 유지한 새로운 state를 만들어주는 도구다.

React는 상태 변경을 “값이 바뀌었다”가 아니라 참조(reference)가 바뀌었는지로 감지한다.

 

const [user, setUser] = useState({
  name: 'Josh',
  profile: { age: 30 },
})

function updateAge() {
  user.profile.age = 31     // ❌ 원본 수정
  setUser(user)             // 참조 동일
}

 

 

문제점

  1. user.profile.age 값은 바뀌었지만
  2. user 객체 자체의 참조는 그대로라서
  3. React 입장에서는 “상태가 바뀌었는지” 판단이 애매하거나, 최적화 상황에서 변경을 놓칠 수 있음

결과적으로:

  • 리렌더가 안 되거나,
  • 특정 컴포넌트만 옛 값으로 남거나,
  • memo/selector/derived state와 섞이면 버그가 더 심해질 수 있음

 

 


얕은 복사(Shallow Copy)로 불변 업데이트

React에서 안전하게 업데이트하려면 바뀌는 경로의 객체를 새로 만들고 나머지는 재사용해야 한다.

setUser(prev => ({
  ...prev,
  profile: {
    ...prev.profile,
    age: 31,
  },
}))
  • 중첩이 깊어질수록 ...spread가 계속 늘어난다
  • 어느 레벨에서 복사를 빼먹으면 버그가 조용히 생긴다
  • 조건/분기가 많아지면 업데이트 코드가 “로직”보다 “복사”가 더 커져 가독성이 무너진다.

 

Immer로 해결: “수정하는 것처럼 쓰되, 결과는 불변 업데이트”

Immer의 핵심 사용법은 produce(baseState, recipe) 형태다.

  • baseState(prev) : 원본(절대 직접 변경되지 않음)
  • recipe(draft) : 수정 가능한 초안(draft)에 업데이트 로직 작성
  • 반환값 : Immer가 만들어 준 새로운 불변 state
import { produce } from 'immer'

setUser(prev =>
  produce(prev, draft => {
    draft.profile.age = 31
  })
)
  • draft는 “마음껏 수정해도 되는 임시 초안”
  • prev(원본)는 실제로 바뀌지 않는다
  • Immer가 변경된 부분만 새 객체로 만들고, 나머지는 기존 참조를 재사용한다
    (이걸 구조적 공유(structural sharing) 라고 함)

즉, 개발자는 “무엇을 바꿀지”에 집중한다.

Immer는 state를 직접 고치는 것처럼 코드를 작성해도, 실제로는 draft(초안)에만 변경을 기록하고,
변경된 부분만 새 객체로 만들어 불변성을 유지해주는 도구이다.

반응형

'개발이야기 > React' 카테고리의 다른 글

useActionState, useEffectEvent  (0) 2025.12.15
ref - DOM 직접 접근 장치  (0) 2025.11.25
이벤트 핸들링  (0) 2025.11.17
컴포넌트  (0) 2025.11.17
render(), virtual DOM, JSX  (0) 2025.11.10