이전 글에서 언급했던 것 처럼 전체 스토어를 구독하게 되면, 스토어에 담고 있는 모든 state가 변경될 때마다 구독중인 컴포넌트가 불필요하게 렌더링된다.
따라서 스토어 전체구독이 아닌, 필요한 state만 구독해서 사용해야한다.
// 전체 스토어를 구독 --> 불필요한 리렌더링 발생
const {bears} = useBearStore()
// 필요한 state만 구독 --> 해당 state변경시에만 리렌더링 발생
const bears = useBearStore((state) => state.bears)
추가적으로 이때 zustand에서 제공되는 shallow
함수를 사용하면 해당 스테이트의 값이 이전값과 다른지 비교후 달라졌을 때만 렌더링을 시켜준다.
import shallow from 'zustand/shallow'
// ⬇️ much better, because optimized
const { bears, fish } = useBearStore(
(state) => ({ bears: state.bears, fish: state.fish }),
shallow
)
기존에는 위와 같이 store에서 값을 가져올 때, 2번째 인자로 비교함수로 shallow를 전달하는 방식을 사용했고, 구글링했을 때에도 가장 많이 나왔다.
하지만 이는 아래 이미지에서 확인할 수 있듯이 2023년 10월부터 useShallow를 사용하도록 바뀌었다.
공식사이트에도 이에 대한 내용이 없어서 조금 헤멨는데 더 찾아봤더니
공식사이트에는 업데이트되어있지 않고, Readme.md 와 직접 코드를 확인해서 찾을 수 있었다!
shallow 사용에 대해서 가장 도움된 블로그 글 Zustand 현명하게 사용하기 (불필요한 리렌더링 막기)은 2023년 6월에 작성되어 기존의 shallow
를 비교함수를 전달하는 방식을 직접 useShallow
를 커스텀훅으로 만드셔서 한 줄로 짧게 사용하고 계시기도했는데, 개인적으로 프로젝트에서는 가독성이 좋은 업데이트된 공식의 사용방식을 사용했다.
import shallow from 'zustand/shallow'
// Object pick, re-renders the component when either state.nuts or state.honey change
const { nuts, honey } = useBearStore(
useShallow((state) => ({ nuts: state.nuts, honey: state.honey })),
)
// Array pick, re-renders the component when either state.nuts or state.honey change
const [nuts, honey] = useBearStore(
useShallow((state) => [state.nuts, state.honey]),
)
// Mapped picks, re-renders the component when state.treats changes in order, count or keys
const treats = useBearStore(useShallow((state) => Object.keys(state.treats)))
위 방식이 공식에서 제안하는 세가지 방식으로 state.nuts나 state.honey의 값이 바뀔 때만 해당 명령어가 있는 컴포넌트의 리렌더링이 발생한다.
나는 개인적으로 Array방식을 프로젝트에 사용했다.
다음 글에서는 zustand에서 다른 컴포넌트에서 state값이 바뀌었을 때 리렌더링을 최소화하면서 바로바로 값을 읽고 바꾸는 방법에 대해 다뤄보겠다.