Skip to content
뒤로가기

아니요, React Context는 너무 많은 렌더링을 유발하지 않습니다

게시된 날짜:  at 

React Context 렌더링 인트로 이미지

(이 글은 No, react context is not causing too many renders 아티클을 읽고 스터디 발표를 위해 정리한 글입니다.)


이 아티클이 얘기하고자 하는 바


예시 코드

1. 애플리케이션 구조

export function ReactRenders1() {
  const [value, setValue] = React.useState("foo");

  return (
    <MyProvider>
      <button
        onClick={() => {
          setValue(`${Math.random()}`);
        }}
        className="global-render-button"
      >
        {" "}
        Render all
      </button>

      <div className="render-tracker-demo">
        <StateChanger />
        <StateDisplayer />

        <SomeUnrelatedComponent />
        <SomeUnrelatedComponent />
        <SomeUnrelatedComponent />
      </div>
    </MyProvider>
  );
}

2. MyProvider - Context 제공자

const MyProvider = ({ children }: { children: React.ReactNode }) => {
  const [value, setValue] = React.useState("foo");
  const contextValue: MyContextType = { value, setValue };

  return (
    <MyContext.Provider value={contextValue}>{children}</MyContext.Provider>
  );
};

+) React 19부터는 <SomeContext>를 Provider로 사용할 수 있음

return <MyContext value={contextValue}>{children}</MyContext>;

3. StateChangerStateDisplayer - Context 사용자

function StateChanger() {
  const { setValue } = useContext(MyContext);

  return (
    <div className="state-changer">
      <strong>State Changer</strong>

      <button onClick={() => setValue(`${Math.random()}`)}>Change state</button>
      <RenderTracker />
    </div>
  );
}

function StateDisplayer() {
  const { value } = useContext(MyContext);

  return (
    <div className="state-displayer">
      <strong>State Displayer</strong>
      <div>{value}</div>
      <RenderTracker />
    </div>
  );
}

4. SomeUnrelatedComponent - Context를 사용하지 않는 컴포넌트

function SomeUnrelatedComponent() {
  return (
    <div className="some-unrelated-component">
      <strong>Some unrelated component</strong>
      <RenderTracker />
    </div>
  );
}

5. RenderTracker

export function RenderTracker() {
  let randX = Math.floor(Math.random() * 100);
  let randY = Math.floor(Math.random() * 100);

  return (
    <div className="render-tracker">
      <strong>Render Tracker</strong>
      <div
        className="render-tracking-dot"
        style={{ top: `${randY}%`, left: `${randX}%` }}
      ></div>
    </div>
  );
}

이 컴포넌트는 렌더링될 때마다 다른 위치에 점을 표시하여 렌더링 여부를 시각적으로 보여줍니다.


실험 결과

Render all 버튼 클릭 시 Change state 버튼 클릭 시

⇒ 즉, Context 상태가 변경될 때마다 Provider 아래의 모든 것이 다시 렌더링된다고 생각은 오해입니다. (사진과 같이 SomeUnrelatedComponent는 다시 렌더링되지 않음)


Context 오해의 원인

1. 모든 상태를 하나의 Provider에 넣는 경우

function FooComponent() {
  const { color, setColor } = useContext(MyContext);

  return (
    <div className="foo-component">
      <strong>Foo Component</strong>
      <button
        onClick={() => {
          // This is Copilots suggestion lol
          const randomColor = `#${Math.floor(Math.random() * 16777215).toString(
            16
          )}`;
          setColor(randomColor);
        }}
      >
        Randomize color
      </button>
      <div className="color-display" style={{ backgroundColor: color }}></div>
      <RenderTracker />
    </div>
  );
}
FooComponent color 변경 시

2. children Prop의 오해

export function ReactRenders3() {
  return (
    <div className="render-tracker-demo">
      <ChildrenStyleOne />

      <ChildrenStyleTwo>
        <RenderTracker />
      </ChildrenStyleTwo>
    </div>
  );
}
export function ChildrenStyleOne() {
  const [value, setValue] = React.useState(0);

  return (
    <div className="some-parent-component">
      <strong>ChildrenStyleOne</strong>
      <button
        onClick={() => {
          setValue(prev => prev + 1);
        }}
      >
        Increase count: {value}
      </button>
      {/* 👇 여기서는 RenderTracker를 직접 선언 */}
      <RenderTracker />
    </div>
  );
}
ChildrenStyleOne 구조 ChildrenStyleOne 동작
export function ChildrenStyleTwo(props: React.PropsWithChildren) {
  const [value, setValue] = React.useState(0);

  return (
    <div className="some-parent-component">
      <strong>ChildrenStyleTwo</strong>
      <button
        onClick={() => {
          setValue(prev => prev + 1);
        }}
      >
        Increase count: {value}
      </button>
      {/* 👇 여기서는 children prop으로 전달됨 */}
      {props.children}
    </div>
  );
}
ChildrenStyleTwo 구조 ChildrenStyleTwo 동작

이해

  • ReactRenders3가 내려보내는 children이 같은 React element 참조를 유지하기 때문에, ChildrenStyleTwo에서는 RenderTracker가 리렌더되지 않는다.
    • 왜? ⇒ RenderTrackerChildrenStyleTwo에서 생성되는게 아니라 ReactRenders3에서 생성되고, ChildrenStyleTwo는 단순히 배치만 하기 때문이다.

따라서, ChildrenStyleTwo가 리렌더되어도 children prop인 RenderTracker는 리렌더되지 않는다!


결론 및 조언

제어 컴포넌트 타이핑 시

마인드맵 정리

React Context 오해와 성능 이해

수정 제안하기


다음 게시글
커스텀 Hook으로 로직 재사용하기