Updating state

김동현·2026년 3월 4일

상태 업데이트하기 (Updating state)

안녕하세요! 프론트엔드 개발의 세계로 오신 것을 환영합니다. 오늘은 전역 상태 관리 라이브러리인 Zustand의 핵심 중 하나인 상태 업데이트(Updating state) 에 대해 공식 문서를 바탕으로 함께 공부해볼 거예요. 영어가 조금 부담스러우셨다니, 제가 이해하기 쏙쏙 편하게 구어체로 설명해 드리겠습니다. 자, 시작해볼까요?


얕은 병합을 통한 단일 레벨 업데이트 (Flat updates)

Zustand로 상태를 업데이트하는 건 정말 간단해요! 제공된 set 함수를 새로운 상태값과 함께 호출하기만 하면 됩니다. 그러면 Zustand가 알아서 스토어에 있는 기존 상태와 얕은 병합(shallow merged) 을 해준답니다.

강사의 부연 설명 💡 > 여기서 '얕은 병합(shallow merge)'이란 1차원적인 속성들만 덮어씌워진다는 뜻이에요. 기존에 firstName, lastName, age 상태가 있었는데 set({ firstName: '홍' })만 호출하면, lastNameage는 날아가지 않고 그대로 유지되면서 firstName만 쏙 바뀐다는 거죠. React의 setStateuseState 객체 업데이트보다 훨씬 편하죠?

참고: 중첩된(nested) 상태를 다루는 방법은 다음 섹션에서 바로 설명해 드릴게요.

import { create } from 'zustand'

type State = {
  firstName: string
  lastName: string
}

type Action = {
  updateFirstName: (firstName: State['firstName']) => void
  updateLastName: (lastName: State['lastName']) => void
}

// 스토어 생성하기 (상태와 액션을 모두 포함합니다)
const usePersonStore = create<State & Action>((set) => ({
  firstName: '',
  lastName: '',
  updateFirstName: (firstName) => set(() => ({ firstName: firstName })),
  updateLastName: (lastName) => set(() => ({ lastName: lastName })),
}))

// 앱에서 사용할 때
function App() {
  // 필요한 상태와 액션만 "선택(select)" 해서 가져옵니다.
  // 이 경우엔 firstName 값과 updateFirstName 액션을 가져왔네요.
  const firstName = usePersonStore((state) => state.firstName)
  const updateFirstName = usePersonStore((state) => state.updateFirstName)

  return (
    <main>
      <label>
        First name
        <input
          // "firstName" 상태를 업데이트합니다.
          onChange={(e) => updateFirstName(e.currentTarget.value)}
          value={firstName}
        />
      </label>

      <p>
        Hello, <strong>{firstName}!</strong>
      </p>
    </main>
  )
}

깊게 중첩된 객체 (Deeply nested object)

만약 다음과 같이 아주 깊은 구조를 가진 상태 객체가 있다면 어떨까요?

type State = {
  deep: {
    nested: {
      obj: { count: number }
    }
  }
}

이런 중첩된 상태를 업데이트하려면, 원본 데이터를 훼손하지 않는 불변성(immutability) 을 지키기 위해 어느 정도 수고가 필요합니다.

실무 꿀팁 🍯 > 자바스크립트에서 중첩된 객체를 다룰 때는 객체의 참조(reference)가 얽히지 않도록 조심해야 해요! 원본 객체의 값을 직접 수정해버리면(mutation), React나 Zustand는 값이 변했다는 걸 감지하지 못해서 화면이 리렌더링되지 않는 무서운 버그를 마주하게 될 수 있습니다.

일반적인 방법 (Normal approach)

React나 Redux에서 보통 하던 것과 비슷하게, 가장 보편적인 방법은 상태 객체의 각 깊이(level)별로 일일이 복사본을 만드는 거예요. 전개 연산자(spread operator, ...)를 사용해서, 기존 값들을 펼쳐놓고 새로운 상태값을 수동으로 병합하는 거죠. 아래 코드처럼요:

  normalInc: () =>
    set((state) => ({
      deep: {
        ...state.deep,
        nested: {
          ...state.deep.nested,
          obj: {
            ...state.deep.nested.obj,
            count: state.deep.nested.obj.count + 1
          }
        }
      }
    })),

어휴, 코드가 정말 길고 복잡하죠? 깊이가 깊어질수록 실수하기 딱 좋습니다. 여러분의 개발 라이프를 조금 더 편하게 만들어 줄 몇 가지 훌륭한 대안들을 함께 살펴보죠!


Immer 사용하기 (With Immer)

정말 많은 개발자들이 중첩된 값을 업데이트할 때 Immer라는 라이브러리를 애용합니다. Immer는 React, Redux는 물론이고 Zustand처럼 불변성을 지키면서 중첩된 상태를 업데이트해야 하는 모든 곳에서 빛을 발하죠!

Immer를 사용하면 깊게 중첩된 객체의 상태 업데이트 코드를 획기적으로 줄일 수 있답니다. 예시를 한번 볼까요?

  immerInc: () =>
    set(produce((state: State) => { ++state.deep.nested.obj.count })),

와! 엄청나게 줄어들었죠? 직접 값을 수정하는 것처럼 코드를 작성해도 Immer가 알아서 불변성을 유지하는 새로운 객체로 만들어주기 때문이에요.

강사의 강력 추천 👍 > 실무에서도 Zustand를 쓸 때, 상태가 2~3단계만 깊어져도 거의 무조건 Immer 미들웨어를 붙여서 사용합니다. 코드가 간결해지는 건 물론이고 읽기도 훨씬 편해져서 유지보수에 정말 큰 도움이 되거든요! 꼭 도입해 보시는 걸 추천합니다.

단, Zustand와 Immer를 함께 쓸 때 주의해야 할 몇 가지 사항(gotchas)이 있으니 꼭 한번 읽어보시길 바랄게요.


optics-ts 사용하기 (With optics-ts)

또 다른 선택지로 optics-ts라는 라이브러리를 활용하는 방법도 있습니다. 함수형 프로그래밍의 '렌즈(Lens)' 개념을 사용하는 건데요:

  opticsInc: () =>
    set(O.modify(O.optic<State>().path("deep.nested.obj.count"))((c) => c + 1)),

Immer와는 다르게, optics-ts는 프록시(proxies) 기술이나 직접 상태를 변경(mutation)하는 듯한 문법을 사용하지 않습니다. 순수하게 함수형 방식을 선호하신다면 좋은 선택이 될 수 있어요.


Ramda 사용하기 (With Ramda)

유명한 유틸리티 라이브러리인 Ramda를 사용할 수도 있답니다:

  ramdaInc: () =>
    set(R.modifyPath(["deep", "nested", "obj", "count"], (c) => c + 1)),

위에서 소개한 ramdaoptics-ts 두 가지 방법 모두 타입스크립트의 타입(types)을 완벽하게 지원하므로 안전하게 사용할 수 있습니다. 팀의 코딩 컨벤션이나 취향에 맞춰서 골라 쓰시면 됩니다.


데모 (Demo)

지금까지 배운 내용들이 실제로 어떻게 동작하는지 아래 링크에서 직접 확인하고 코드를 만져보실 수 있습니다!

https://stackblitz.com/edit/vitejs-vite-j6bjdygu


이전 튜토리얼: 틱택토 (Tutorial: Tic-Tac-Toe)

다음 스토어 액션 없이 연습하기 (Practice with no store actions)

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

0개의 댓글