최근 쉽고 빠르게 저장소를 구축할 때 zustand를 많이 사용하는 듯 하다. 사용하는 방법도 매우 간편하다.
const useStore = create((set) => ({
count: 0,
num: 1,
setNum: () => set({ num: 1 }),
increase: () => set((state) => ({ count: state.count + 1 })),
decrease: () => set((state) => ({ count: state.count - 1 }))
}))
컨텍스트 API나 Redux 등 대부분의 스토어를 사용하게 되면 Provider를 통해서 상태를 관리하고 하위 컴포넌트에게 상태를 전달하는 방식을 사용한다.
하지만 Zustand는 Provider를 사용하지 않는다. 위의 코드와 같이 스토어만 만들면 다른 설정 필요 없이 즉시 컴포넌트에서 데이터를 가져다 쓸 수 있다.
컴포넌트에서 아래와 같이 셀렉터 함수를 사용해서 값을 가져올 수 있다.
const Count = () => {
const count = useStore((state) => state.count);
const num= useStore((state) => state.num);
}
위 처럼 하나씩 가져오면 코드가 길어지고 번거롭기 때문에 여러개의 상태를 한번에 가져오고 싶을 수 있다.
const Count = () => {
const { count, num } = useStore((state) => ({
count: state.count,
num: state.num
}));
}
위의 코드를 실행하게 되면 아래와 같은 에러를 만나게 된다.
🚫 Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
🎈 무한 루프 해결하기
위 문제가 왜 발생하는지 알아봐기 위해서 zustand의 리렌더링 방식을 알아야 한다.
기본적으로 zustand에서는 셀렉터 함수를 사용하지 않으면 모든 상태를 반환해준다.
const Count = () => {
const { count } = useStore();
}
위와 같은 방식을 사용하게 되면 내가 사용하지 않는 상태가 변경이 되어도 리렌더링된다. 왜냐하면 어떠한 상태를 사용하는지를 알 수 없기 때문이다.
그래서 내가 사용하지 않는 상태가 변경될때는 리렌더링이 되지 않도록 하기 위해서는 셀렉터 함수를 사용해야 한다.
const Count = () => {
const count = useStore((state) => state.count);
}
그렇다면 zustand에서는 어떠한 방식으로 상태가 변경되었는지를 감지하는 것인가?
그건 바로 셀렉터 함수를 호출 후 반환받은 값과 이전에 셀렉터 함수를 호출했을 때 반환받은 값을 비교 후 변경사항이 존재하면 해당 컴포넌트를 리렌더링하게 된다.
위의 설명으로부터 무한루프가 왜 발생했는지를 알 수 있다. 다시 한번 코드를 보자. 아래의 코드에서 보면 셀렉터 함수를 호출 후 반환받는 객체는 매번 새롭게 생성이 된다. 즉, 이로인해 매번 값이 변경된 것으로 감지가 되어 리렌더링이 무한으로 발생하게 된다.
const Count = () => {
const { count, num } = useStore((state) => ({
count: state.count,
num: state.num
}));
}
이러한 문제를 해결하기 위해 useShallow 훅을 제공해준다. 셀렉터 함수를 useShallow 훅에 전달해주면 된다. 그럼 실제 값이 변경되지 않는 경우 리렌더링 되지 않는다.
const Count = () => {
const { count, num } = useStore(useShallow((state) => ({
count: state.count,
num: state.num
})));
}
공식 문서에는 해당 설명이 잘 나와있지 않지만, 깃허브에서 확인이 가능하다. 공식문서를 한참 뒤졌는데 못찾았다가 깃허브에서 찾았다..
❤ 잠깐 상식!
원시값과는 달리 객체는 === 비교 시 속성명과 값이 모두 일치하더라도 false 가 반환된다. 이 때문에 다른 값으로 인식하게 된다.
const obj1 = { name: 1 };
const obj2 = { name: 1 };
console.log(obj1 === obj2); // false
'React' 카테고리의 다른 글
[React] memo, useCallback 사용해보기 (0) | 2025.04.16 |
---|---|
[React] 함수를 상태에 저장하기 (0) | 2025.03.19 |
[React] Context API 성능개선 (0) | 2025.02.15 |
리액트 팁 및 클린코드 - 4. 기타 (0) | 2024.11.02 |
리액트 팁 및 클린코드 - 3. Props (0) | 2024.10.26 |