Immutable state and merging

김동현·2026년 3월 4일

불변 상태(Immutable state)와 병합(Merging)

안녕하세요! 여러분의 프론트엔드 성장을 돕는 강사입니다. 오늘은 Zustand의 공식 문서를 통해 상태 관리의 핵심인 '불변성 유지'와 Zustand만의 편리한 기능인 '상태 병합'에 대해 아주 자세히 파헤쳐 볼 거예요. 편하게 읽으실 수 있도록 설명과 꿀팁을 팍팍 담아보았습니다. 자, 시작해 볼까요?


React에서 useState를 사용할 때처럼, Zustand에서도 상태(state)를 업데이트할 때는 반드시 불변성(Immutability) 을 유지해야 해요.

💡 강사의 보충 설명:
불변성을 유지한다는 건, 기존 상태 객체를 직접 수정하는 게 아니라 새로운 객체를 만들어서 교체해 준다는 뜻이에요. 왜 이렇게 해야 할까요? React나 Zustand 같은 도구들은 상태가 바뀌었는지 확인할 때 얕은 비교(참조값 비교)를 합니다. 껍데기(메모리 주소)가 새로워져야 "아, 상태가 변경되었구나!" 하고 알아차리고 화면을 렌더링해 주기 때문이죠.

자, 가장 전형적인 예시 코드를 먼저 살펴볼까요?

import { create } from 'zustand'

const useCountStore = create((set) => ({
  count: 0,
  inc: () => set((state) => ({ count: state.count + 1 })),
}))

위 코드에서 set 함수는 스토어(store) 안의 상태를 업데이트하는 역할을 해요. 상태는 불변성을 띄어야 하기 때문에, 원칙적으로 따지면 아래처럼 코드를 작성해야 맞습니다.

set((state) => ({ ...state, count: state.count + 1 }))

하지만, 상태의 일부만 업데이트하는 건 너무나도 자주 쓰이는 패턴이잖아요? 그래서 우리의 Zustand는 set 함수가 실제로 상태를 자동으로 병합(merge) 해주도록 설계되었습니다. 덕분에 우리는 귀찮게 ...state를 써서 기존 상태를 복사하는 부분을 생략할 수 있어요!

set((state) => ({ count: state.count + 1 }))

💡 강사의 꿀팁:
React의 useState로 객체 상태를 다룰 때 매번 ...state를 해줘야 해서 번거로우셨죠? Zustand는 최상위 객체에 한해서 이 작업을 대신 해줍니다. 코드가 훨씬 깔끔해지는 마법이죠. 실무에서 코드를 읽고 쓸 때 피로도를 크게 줄여줍니다!


중첩된 객체 (Nested objects)

여기서 정말 주의해야 할 점이 하나 있어요! set 함수의 자동 병합 기능은 딱 한 단계(1 depth)까지만 동작합니다.

만약 상태 안에 또 다른 객체가 들어있는 중첩 객체(Nested object) 구조라면, 직접 명시적으로 병합을 해줘야 해요. 이때는 우리가 익숙한 스프레드 연산자(...) 패턴을 아래처럼 사용해야 합니다.

import { create } from 'zustand'

const useCountStore = create((set) => ({
  nested: { count: 0 },
  inc: () =>
    set((state) => ({
      nested: { ...state.nested, count: state.nested.count + 1 },
    })),
}))

💡 강사의 보충 설명:
Zustand는 '얕은 병합(Shallow Merge)'만 지원하기 때문이에요. 객체 안의 객체까지 샅샅이 뒤져서 병합해 주진 않습니다. 따라서 중첩된 nested 객체를 업데이트할 때는 ...state.nested를 통해 기존 중첩 객체의 다른 데이터들을 잃어버리지 않게 먼저 복사해 와야만 안전합니다.

실무에서는 상태 구조가 예시보다 훨씬 복잡하고 깊어지는 경우가 많습니다. 이런 복잡한 상황에서는 불변성 업데이트를 도와주는 외부 라이브러리의 사용을 고려해 보는 것이 좋아요. 관련된 내용은 중첩된 상태 객체 값 업데이트하기(Updating nested state object values) 문서를 참고해 보세요.

💡 강사의 실무 꿀팁:
뎁스(Depth)가 2, 3 이상으로 깊어지는 객체를 다룰 때 계속 스프레드 연산자를 쓰면 코드가 지저분해지고 실수하기 쉽습니다. 저는 개인적으로 Zustand와 Immer(이머) 라이브러리를 함께 사용하는 것을 강력히 추천합니다!
Zustand는 Immer를 위한 미들웨어를 내장하고 있어요. Immer를 쓰면 기존 객체를 직접 수정하는 것처럼 코드를 짜도(예: state.nested.count++) 알아서 내부적으로 불변성을 지켜주기 때문에 개발 생산성이 엄청나게 올라갑니다.


Replace 플래그 (병합 비활성화하기)

Zustand의 편리한 자동 병합 기능을 아예 끄고 싶을 때도 있겠죠? 그럴 때는 set 함수의 두 번째 인자로 replace를 뜻하는 불리언(boolean) 값, 즉 true를 전달해 주면 됩니다.

set((state) => newState, true)

💡 강사의 보충 설명:
자동 병합을 끄는 이 기능, 언제 쓸까요? 보통 사용자 로그아웃 처리를 할 때 스토어의 모든 상태를 싹 다 초기값으로 되돌리고 싶거나, 서버에서 받아온 완전히 새로운 구조의 데이터로 스토어를 통째로 갈아 끼워야 할 때 유용하게 쓰입니다.
set의 두 번째 인자 기본값은 false(병합 활성화)인데, 여기에 true를 주면 기존 상태와 섞이지 않고 newState 값으로 완전히 대체(Replace)되는 원리입니다.


네비게이션

profile
프론트에_가까운_풀스택_개발자

0개의 댓글