안녕하세요! 프론트엔드 개발 학습을 도와드리는 AI 강사 제미나이입니다. Zustand 공식 문서를 보며 열심히 공부하시는 모습이 정말 멋지네요!
문서가 영어로 되어 있어서 조금 막막하셨죠? 요청하신 대로 단 한 줄도 요약하지 않고, 원본의 모든 내용을 이해하기 쉬운 구어체로 꼼꼼하게 번역해 드릴게요. 제가 AI로서 수많은 프론트엔드 코드와 패턴을 학습하며 얻은 '실무 팁'과 부연 설명도 중간중간 팍팍 넣어두었으니, 천천히 읽어보시면 훨씬 이해가 잘 되실 거예요. 자, 그럼 함께 살펴볼까요?
Map과 Set은 자바스크립트에서 값을 직접 변경할 수 있는 '가변(mutable)' 자료구조예요. Zustand 상태 관리에서 이 친구들을 사용할 때는 상태를 업데이트할 때마다 반드시 새로운 인스턴스를 생성해서 넣어주어야 한답니다. (왜 그래야 하는지는 아래쪽에서 자세히 설명해 드릴게요!)
스토어에서 Map 객체를 읽어오는 방법은 일반적인 상태를 가져오는 것과 완전히 똑같아요. 단순하게 상태의 키값을 지정해주면 됩니다.
const foo = useSomeStore((state) => state.foo)
여기가 아주 중요해요! Map의 데이터를 변경할 때는 기존 Map을 그대로 수정하지 말고, 항상 새로운 Map 인스턴스를 만들어야 합니다.
// 단일 항목 업데이트하기 (Update single entry)
set((state) => ({
foo: new Map(state.foo).set(key, value),
}))
// 항목 삭제하기 (Delete entry)
set((state) => {
const next = new Map(state.foo)
next.delete(key)
return { foo: next }
})
// 여러 항목 한 번에 업데이트하기 (Update multiple entries)
set((state) => {
const next = new Map(state.foo)
next.set('key1', 'value1')
next.set('key2', 'value2')
return { foo: next }
})
// 모두 지우기 (Clear)
set({ foo: new Map() })
💡 AI 강사의 실무 팁: > 자바스크립트에서
new Map(기존_Map)을 호출하면 기존 Map의 데이터를 그대로 가진 새로운 복사본(얕은 복사)이 만들어져요. 이렇게 복사본을 만든 다음에set이나delete를 통해 원하는 데이터만 조작하고 반환하는 패턴이 가장 정석적이랍니다! 객체(Object) 대신 Map을 쓰면 키(Key) 값으로 문자열뿐만 아니라 객체나 숫자 등 다양한 타입을 쓸 수 있어서 복잡한 데이터를 다룰 때 아주 유용해요.
Set을 읽어올 때도 Map이나 다른 상태를 읽어올 때처럼 평범하게 꺼내 오시면 됩니다.
const bar = useSomeStore((state) => state.bar)
Set의 데이터를 변경할 때도 Map과 마찬가지로 항상 새로운 Set 인스턴스를 생성해 주어야 해요.
// 항목 추가하기 (Add item)
set((state) => ({
bar: new Set(state.bar).add(item),
}))
// 항목 삭제하기 (Delete item)
set((state) => {
const next = new Set(state.bar)
next.delete(item)
return { bar: next }
})
// 항목 토글하기 (Toggle item - 있으면 지우고, 없으면 추가하고)
set((state) => {
const next = new Set(state.bar)
next.has(item) ? next.delete(item) : next.add(item)
return { bar: next }
})
// 모두 지우기 (Clear)
set({ bar: new Set() })
💡 AI 강사의 실무 팁:
Set은 '중복을 허용하지 않는 데이터의 집합'이 필요할 때 최고의 선택이에요! 예를 들어, 사용자가 쇼핑몰에서 선택한 상품들의 ID 목록을 관리할 때 배열(Array)을 쓰면 중복을 체크하기 귀찮은데, Set을 쓰면 알아서 중복이 걸러집니다. 게다가 배열의includes메서드보다 Set의has메서드가 검색 속도도 훨씬 빠르다는 사실, 잊지 마세요!
Zustand는 상태가 정말로 바뀌었는지 감지할 때 참조(Reference)를 비교하는 방식을 사용해요. 기존에 존재하는 Map이나 Set에 내용을 직접 추가하거나 지운다고 해도(mutate), 객체 자체의 메모리 주소(참조)는 전혀 바뀌지 않는답니다. 껍데기는 그대로고 알맹이만 바뀐 셈이죠.
// ❌ 잘못된 방법 - 참조값이 같아서 리렌더링이 발생하지 않아요!
set((state) => {
state.foo.set(key, value)
return { foo: state.foo }
})
// ✅ 올바른 방법 - 새로운 참조값이 생성되므로 리렌더링이 정상적으로 발생해요!
set((state) => ({
foo: new Map(state.foo).set(key, value),
}))
👩🏫 강사의 부연 설명:
이 부분은 리액트(React) 생태계 전체를 관통하는 핵심 개념인 '불변성(Immutability)'과 깊은 관련이 있어요. Zustand뿐만 아니라 리액트 자체도 렌더링 최적화를 위해 객체의 알맹이를 일일이 뒤져서 비교하지 않아요. 대신 "이전 상태 객체 메모리 주소 !== 새로운 상태 객체 메모리 주소" 일 때만 화면을 다시 그리거든요. 그래서 반드시new Map()이나new Set()으로 '새로운 껍데기(새로운 메모리 주소)'를 만들어서 전달해 줘야만 프론트엔드 화면이 정상적으로 업데이트됩니다.
타입스크립트(TypeScript) 환경에서 빈 Map이나 빈 Set을 처음 만들어 줄 때는 반드시 타입 힌트(Type hints)를 제공해 주어야 합니다.
{
ids: new Set([] as string[]),
users: new Map([] as [string, User][])
}
이렇게 타입 힌트를 주지 않으면, 타입스크립트는 빈 배열을 보고 never[] 타입으로 제멋대로 추론해 버려요. 그렇게 되면 나중에 진짜 데이터를 추가하려고 할 때, 타입스크립트가 "여긴 아무것도 못 들어오는 곳이야!"라며 에러를 뱉어내게 되니 주의하세요.
💡 AI 강사의 실무 팁:
공식 문서에 나온as키워드를 사용한 타입 단언(Type Assertion) 방식도 좋지만, 보통 실무에서는 제네릭(Generic)을 사용해서 표현하는 것을 조금 더 선호하는 편이에요.
- Set의 경우:
new Set<string>()- Map의 경우:
new Map<string, User>()
이렇게 작성하면 코드가 좀 더 깔끔해지고, 직관적으로 어떤 타입이 들어갈지 알 수 있어서 유지보수하기 훨씬 좋답니다!
실제로 어떻게 동작하는지 아래 링크에서 직접 코드를 실행하고 확인해 보실 수 있어요.
기본 데모: https://stackblitz.com/edit/vitejs-vite-5cu5ddvx
이전 글 (Previous)
Immutable state and merging
다음 글 (Next)
Prevent rerenders with useShallow