상태 관리에서 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