Context API에 관한 오해

Lybell·2024년 7월 31일
0
post-custom-banner

Context.Provider의 상태가 바뀌면 모든 하위 컴포넌트가 리렌더링된다?

const MyContext = createContext();

function Child1()
{
  console.log("child 1 rerender");
  return <div>Child 1</div>
}
function Child2()
{
  console.log("child 2 rerender");
  const light = useContext(MyContext);
  return <div>{light ? "light" : "dark"}</div>
}

function App() {
  console.log("app rerender");
  const [light, setLight] = useState(true);
  const [count, setCount] = useState(0);

  return (
    <>
      <MyContext.Provider value={light}>
        <Child1 />
        <Child2 />
      </MyContext.Provider>
      <div onClick={()=>setLight( l=>!l )}>변화</div>
      <div onClick={()=>setCount( l=>l+1 )}>{count}</div>
    </>
  )
}

이런 코드를 짰을 때, light 상태를 변화시키면 자식 컴포넌트가 모두 리렌더링되는 경험을 볼 수 있을 것이다. 그래서 사람들은 "Context Provider의 value가 바뀌면 자식이 모두 리렌더링되는구나! 렌더링에 안 좋겠네?"라고 생각하게 될 것이다.

물론, 이 코드에서 "변화" 버튼을 누르면 Child1과 Child2가 리렌더링되는 건 맞으나, Child1이 리렌더링되는 까닭은 Context Provider가 바뀌기 때문이 아니라, 그냥 App의 state가 바뀌었기 때문이다. 부모 컴포넌트의 state가 바뀌면 부모가 렌더링되고, 자식도 같이 렌더링되기 때문이다.

Context.Provider의 상태와 렌더링은 무관하다?

const MyContext = createContext();

function Child1()
{
  console.log("child 1 rerender");
  return <div>Child 1</div>
}
function Child2()
{
  console.log("child 2 rerender");
  const light = useContext(MyContext);
  return <div>{light ? "light" : "dark"}</div>
}

const MemoWrapper = memo( ()=>{
  return <>
    <Child1 />
    <Child2 />
  </>
});

function App() {
  console.log("app rerender");
  const [light, setLight] = useState(true);
  const [count, setCount] = useState(0);

  return (
    <>
      <MyContext.Provider value={light}>
        <MemoWrapper />
      </MyContext.Provider>
      <div onClick={()=>setLight( l=>!l )}>변화</div>
      <div onClick={()=>setCount( l=>l+1 )}>{count}</div>
    </>
  )
}

이 경우에는 변화 버튼을 클릭했을 때, MemoWrapper에 props가 변화하지 않아서 MemoWrapper가 리렌더링되지 않음에도 불구하고, Child2는 리렌더링되고 있다. MyContext.Provider의 value가 바뀌었고, Child2가 MyContext를 소비하고 있기 때문이다.

즉, useContext는 다음의 2개의 역할을 한다.

  • Context 매개변수를 받아서, 가장 가까운 부모의 Context.Provider를 찾아서, 그것의 value를 반환한다.
  • 해당 Context.Provider를 구독하고, value가 바뀔 때 상태 변화와 관계없이 리렌더링을 수행한다.

정리

Context API를 이용해서 상태를 관리할 때, 하위 컴포넌트를 memo로 감쌌을 때와 감싸지 않았을 때의 동작은 다음과 같다.

  • memo로 감싸지 않는 경우
    • Provider 자신 : state가 바뀌었기 때문에 리렌더링된다.
    • 컨텍스트를 소비하는 컴포넌트 : 부모 컴포넌트가 렌더링되기 때문에(+context.provider의 value가 바뀌었기 때문에) 리렌더링된다.
    • 컨텍스트를 소비하지 않는 컴포넌트 : 부모 컴포넌트가 렌더링되기 때문에, 리렌더링된다.
  • memo로 감싸는 경우
    • Provider 자신 : state가 바뀌었기 때문에 리렌더링된다.
    • 컨텍스트를 소비하는 컴포넌트 : context.provider의 value가 바뀌었기 때문에 리렌더링된다.
    • 컨텍스트를 소비하지 않는 컴포넌트 : 리렌더링되지 않는다.
profile
홍익인간이 되고 싶은 꿈꾸는 방랑자
post-custom-banner

0개의 댓글