권현우의 프로필 사진

Hyunwoo

zustand useShallow 언제쓸까?

zustand의 useShallow는 불필요한 리렌더링을 방지하는 데 도움을 줍니다.

2023.11.19

  • javascript
  • zustand
  • react

기본적인 Store 사용

import { create } from 'zustand'
 
// store 생성
const useStore = create((set) => ({
  a: 'a',
  b: 'b',
  c: 'c',
  d: 'd',
}))
 
const Coponent = () => {
  // a,b,c,d state 구독
  const a = useStore((state) => state.a)
  const b = useStore((state) => state.b)
  const c = useStore((state) => state.c)
  const d = useStore((state) => state.d)
 
  return (
    <>
      <p>a:{a} </p>
      <p>b:{b} </p>
      <p>c:{c} </p>
      <p>d:{d} </p>
    </>
  )
}

Zustand를 사용할 때 store의 여러 state를 구독 할때 위 코드내 13~16번째 줄 처럼같이 일일히 하나씩 받아옵니다. 하지만 만 뭔가 좀 무식해 보이지 않나요?

useShallow를 이용한 store 구독

// a,b,c,d state 구독
const [a, b, c, d] = useStore((state) => [state.a, state.b, sate.c, state.d])

그래서 위와 같이 한번에 가져 올 수 있습니다. 하지만 이렇게 한번에 가져올 시 문제가 생깁니다. 어떤 문제가 생기냐면 다른 컴포넌트에서 위와 같은 방식으로 여러 state를 구독했을 시 불필요한 리렌더링이 발 생 할 수 있습니다. 

import { useEffect } from 'react'
import { create } from 'zustand'
 
const useStore = create((set) => ({
  a: 'a',
  b: 'b',
  c: 'c',
  setA: () => set((state) => ({ a: state.a })),
}))
 
export const Parent = () => {
  return (
    <>
      <Child1></Child1>
      <Child2></Child2>
    </>
  )
}
 
const Child1 = () => {
  const [a, setA] = useStore((state) => [state.a, state.setA])
  useEffect(() => {
    console.log('<Child1> 렌더링')
  })
  return (
    <button
      onClick={() => {
        console.log('버튼클릭됨')
        setA()
      }}
    >
      child1
    </button>
  ) // 버튼 클릭시
}
 
const Child2 = () => {
  const [b, c] = useStore((state) => [state.b, state.c])
  useEffect(() => {
    console.log('<Child2> 렌더링')
  })
  return <p>child2</p>
}

위 코드의 Child1 컴포넌트 내부에 button을 클릭시 store의 state 값 a가 변경됩니다. 그래서 a를 구독하고 있는 Child1 컴포넌트만 렌더링 될 것으로 예상되지만 실제로는 a를 구독하고 있지 않은 Child2의 컴포넌트도 렌더링이 됩니다. 이상하죠? 공식문서에서는 불필요한 리렌더링을 막기 위해 useShallow를 쓰라고 권장합니다. 

import { useShallow } from 'zustand/react/shallow'
...
 
const Child1 = () => {
  // useShallow로 감쌈
  const [a, setA] = useStore(useShallow((state) => [state.a, state.setA]))
    useEffect(()=>{
        console.log("<Child1> 렌더링");
    })
    return(<button onClick={()=>{
        console.log("버튼클릭됨");
        setA()
    }}>child1</button>) // 버튼 클릭시
}
 
const Child2 = ()=>{
	// useShallow로 감쌈
    const [b,c] = useStore(useShallow((state)=>([state.b,state.c])))
    useEffect(()=>{
        console.log("<Child2> 렌더링");
    })
    return(<p>child2</p>)
}

요렇게 state를 가져올 때 useShallow로 가져 올 시 구독하지 않은 state가 변하지 않을 경우 재렌더링은 일어나지 않습니다. 물론 맨위에서 일일히 state를 가져오는 방법을 사용할 시 굳이 useShallow는 사용하지 않아도 리렌더링 문제는 발생하지 않습니다. 

참고