React의 useContext를 이용 시 리 렌더링을 막는 방법

최근 useContext를 사용 중 불필요한 리 렌더링을 막는 방법에 관해서 관심이 생겨 직접 확인 해 보았습니다.
2024.05.13

최근 useContext를 사용 중 불필요한 리 렌더링을 막는 방법에 관해서 관심이 생겨 직접 확인 해 보았습니다.

준비

먼저 확인을 위해 샘플 코드를 만들어 보겠습니다.

1.먼저 react 프로젝트를 생성합니다.

npm create vite@latest [project-name] -- --template react-ts

2.확인을 위해 관련 component와 context 코드를 작성합니다.

countContext.tsx

import { createContext, useContext } from 'react';

export interface CountContextType {
    count: number
    countIncrement : () => void
    countDecrement : () => void
}

export const CountContext = createContext<CountContextType | undefined>(undefined);

export const useCountContext = (): CountContextType => {
  const context = useContext(CountContext);

  if (context === undefined) {
    throw new Error('must used within CountProvider');
  }

  return context;
};

Main.tsx

import { useCountContext } from "../countContexts"

export function Main() {
    const { count, countIncrement, countDecrement  } = useCountContext()
    return (
      <div>
        <div>{count}</div>
        <button onClick={countIncrement}>+</button>
        <button onClick={countDecrement}>-</button>
      </div>
    )
}

Header.tsx

export function Header() {
    return (
      <div>HEADER</div>
    )
}

Footer.tsx

export function Footer() {
    return (
      <div>FOOTER</div>
    )
}

App.tsx

import { useCallback, useState } from "react"
import { Header } from "./components/Header"
import { Footer} from "./components/Footer"
import { Main } from './components/Main'
import { CountContext } from "./countContexts"


function App() {
  const [count, setCount] = useState(0)

  const countIncrement = useCallback(() => {
      setCount(count + 1)
  },[count])

  const countDecrement = useCallback(() => {
      setCount(count - 1)
  },[count])

  return (
    <CountContext.Provider value={{ count, countIncrement, countDecrement  }}>
      <Header />
      <Main />
      <Footer />
    </CountContext.Provider>);
}

export default App

일단 베이스가 되는 코드를 작성했습니다.
위 코드를 실행해 보면useContext를 이용하고 있는 Main component뿐만 아닌 FooterHeader component도 함께 리 렌더링 되는 것을 확인 할 수 있습니다.

chrome의 확장 도구인 react developer tools의 profiler를 통해 확인해 보면 아래와 같이 리 렌더링 결과를 확인 할 수 있습니다.

방법1

첫 번째 방법은 React.memo를 이용하는 방법입니다. React.memo를 이용하면 부모 component가 리 렌더링 시 불필요한 리 렌더링을 막을 수 있습니다.
보통 부모 component에서 넘겨주는 props의 변경이 없는 경우 React.memo를 사용하면 불필요한 리 렌더링을 막을 수 있습니다.
이번 Hedaer component의 경우에는 아무 props도 넘겨주지 않았기 때문에 리 렌더링이 불필요한 상태입니다.

그럼, 이전 코드의 Header 코드를 수정해 보겠습니다.

Header.tsx

import React from "react"

export const Header = React.memo(() =>{
    return (
      <div>HEADER</div>
    )
})

위와 같이 코드를 작성 후 profiler에서 결과를 확인하면 아래와 같이 Header component의 경우 리 렌더링이 발생하지 않는 것을 확인 할 수 있습니다.

방법2

두 번째 방법은 별도의 component 안에서 상태 값을 관리하여, App component의 리 렌더링을 막으며,
하위 component 중 useContext를 이용하고 있는 component만 리 렌더링시키는 방법입니다.

그럼, 바로 코드를 작성해 보겠습니다.

countContext.tsx

...

export const CountProvider = ({
  children,
}: PropsWithChildren) => {
    const [count, setCount] = useState(0)

    const countIncrement = useCallback(() => {
        setCount(count + 1)
    },[count])

    const countDecrement = useCallback(() => {
        setCount(count - 1)
    },[count])

    return <CountContext.Provider value={{ count, countIncrement, countDecrement  }}>{children}</CountContext.Provider>;
};

App.tsx

...
function App() {
  return (
    <CountProvider>
      <Header />
      <Main />
      <Footer />
    </CountProvider>
  )
}
...

위 코드를 작성 후 실행해 보면 아래와 같이 useContext를 이용하고 있는 Main component 이외의 component가 리 렌더링 되지 않는 것을 확인 할 수 있습니다.

보충

이외에도 공식 문서를 보면 useMemo를 이용한 방법도 소개하고 있습니다만, 개인적으로 심플 하다고 생각하는 두 가지 방법만 확인해 보았습니다.
관심이 있으시다면 다른 여러 방법을 확인 후 사용하고 싶은 방법을 이용해 보셔도 좋을 듯합니다.

참고자료

react developer tools
react useContext - Optimizing re-renders when passing objects and functions