상태 관리에서 immer가 필요한 이유

2025. 4. 1. 18:16카테고리 없음

728x90

✅ 상태 타입 정의

interface Birth {
  year: string;
  month: string;
  day: string;
}
  • 사용자의 생년월일을 year, month, day로 나눠서 관리합니다.
interface UserState {
  birth: Birth;
  gender: string;
  email: string;
  nickname: string;
  setBirth: (birth: Partial<Birth>) => void;
  setGender: (gender: string) => void;
  setEmail: (email: string) => void;
  setNickname: (nickname: string) => void;
}
  • 전체 사용자 상태와 관련된 필드 정의
  • set 함수들을 통해 각 속성들을 업데이트할 수 있도록 함
  • Partial<Birth>: birth를 부분적으로만 업데이트할 수 있도록 허용 (ex. year만 변경 가능)

✅ Zustand Store 생성

const useUserStore = create<UserState>()(
  immer((set) => ({
    birth: { year: '', month: '', day: '' },
    gender: '',
    email: '',
    nickname: '',
  • create<UserState>()(...): Zustand store를 생성할 때, 타입 안전하게 하기 위해 UserState 제네릭 사용
  • immer(...): immer 미들웨어를 적용하여 상태를 불변성 걱정 없이 직접 수정하듯 코딩 가능

✅ 상태 업데이트 함수들

1. setBirth

setBirth: (birth) =>
  set((state) => {
    Object.assign(state.birth, birth);
  }),
  • birth 객체 일부만 전달해도 나머지는 유지하면서 업데이트
  • 예: setBirth({ year: '2000' }) → 기존 month, day는 유지됨
  • Object.assign: 기존 state.birth 객체에 새로운 값 merge

2. setGender

setGender: (gender) =>
  set((state) => {
    state.gender = gender;
  }),
  • 성별 문자열을 직접 변경

3. setEmail

setEmail: (email) =>
  set((state) => {
    state.email = email;
  }),
  • 이메일 문자열을 업데이트

4. setNickname

setNickname: (nickname) =>
  set((state) => {
    state.nickname = nickname;
  }),
  • 닉네임 문자열 업데이트

✅ export

export default useUserStore;
  • 어디서든 useUserStore() 호출하여 이 상태를 구독 및 업데이트할 수 있도록 export

✅ 사용 예시 (컴포넌트에서)

const MyComponent = () => {
  const { birth, setBirth } = useUserStore();

  return (
    <input
      value={birth.year}
      onChange={(e) => setBirth({ year: e.target.value })}
    />
  );
};

✅ 결론

  • ✅ 가볍고 효율적인 상태 관리
  • ✅ immer로 간결하고 안전한 상태 업데이트
  • ✅ Partial<Birth>로 유연한 객체 업데이트.

✅ 상태 관리에서 immer가 필요한 이유

React에서는 상태(state)를 **불변성(immutability)**을 지키면서 관리해야 합니다.
즉, 상태를 직접 수정하면 안 되고, 항상 새로운 객체를 만들어서 교체해야 하죠.

🧊 문제점: 깊은 상태 구조에서의 복잡성

예를 들어 이런 상태가 있다고 해봐요:

const state = {
  user: {
    birth: {
      year: '2000',
      month: '01',
      day: '01',
    },
  },
};

이 상태에서 year만 바꾸고 싶을 때, 불변성을 지키며 바꾸려면 아래처럼 작성해야 해요:

const newState = {
  ...state,
  user: {
    ...state.user,
    birth: {
      ...state.user.birth,
      year: '1999',
    },
  },
};

👀 굉장히 번거롭고 코드 가독성도 떨어지죠.


✅ immer의 등장

immer는 불변성을 유지하면서도 마치 가변처럼 작성할 수 있게 도와주는 라이브러리입니다.

위 코드를 immer로 바꾸면 이렇게 됩니다:

import produce from 'immer';

const newState = produce(state, (draft) => {
  draft.user.birth.year = '1999';
});
  • draft는 상태의 "복사본"이며, 이를 직접 수정하면 immer가 내부적으로 변경 사항을 감지해서 불변성을 유지한 새로운 객체를 만들어줍니다.
  • 즉, 사용자는 그냥 가변처럼 쓰면 되고, immer가 알아서 안전하게 처리해줍니다.

✅ zustand와 immer의 조합

zustand 자체는 상태 업데이트 시 직접 상태를 새 객체로 반환하는 방식으로 작동해요.

하지만 복잡한 상태를 다룰 때 immer 없이 상태를 직접 업데이트하려면, 이런 식으로 작성해야 하죠:

set((state) => ({
  ...state,
  user: {
    ...state.user,
    birth: {
      ...state.user.birth,
      year: '1999',
    },
  },
}));

 

그래서 등장한 조합

const useStore = create(
  immer((set) => ({
    setBirth: (birth) =>
      set((state) => {
        Object.assign(state.birth, birth);
      }),
  }))
);
  • zustand/middleware/immer를 사용하면 set() 안에서 state를 직접 수정하듯 작성해도 불변성을 자동으로 지켜줘요.
  • Object.assign(state.birth, birth) 이런 것도 가능해지고
  • state.user.name = 'John'처럼 직접 할당해도 OK

✅ 왜 immer를 쓰는가?

이유 설명

✅ 직관적인 코드 객체 깊이에 상관없이 state.prop = value처럼 쉽게 수정 가능
✅ 코드 간결성 전개 연산자(...), 깊은 복사 없이 간단하게 상태 업데이트 가능
✅ 불변성 자동 유지 immer가 내부적으로 produce를 사용해 안전하게 상태 생성
✅ zustand의 가벼움 유지 zustand + immer 조합은 recoil, redux-toolkit 대비 여전히 가벼움
✅ 실수 방지 불변성 실수로 인한 상태 꼬임이나 버그를 방지

\

728x90