IT일상

React Context를 쓰면 전체가 리렌더링된다고?

  • 프론트엔드
Profile picture

Written by solo5star

2023. 4. 30. 19:34

결론부터 먼저 말씀드리자면, 내용물 끌어올리기를 적용하였다면 그렇지 않습니다.

const App = () => {
  const [mousePosition, setMousePosition] = useState<[number, number]>([0, 0]);

  // 마우스 움직임을 감지하여 mousePosition 상태 계속 업데이트
  useEffect(() => {
    document.addEventListener('mousemove', (event) => {
      setMousePosition([event.clientX, event.clientY]);
    });
  }, []);

  return (
    <MouseMovementContext.Provider value={mouseMovementContextValue}>
      <Header />

      <Article title="오늘도 즐거운 하루" content="오늘 새벽에 좀 많이 추워서 그런지 아침에 힘들었어요. 일교차가 크니 조심하도록 합시다.">
        <MousePositionMeter />
      </Article>
      <Article title="냉동피자가 4천원이면 꽤 괜찮은데?" content="피자치즈 얹어먹으면 더더욱 맛있습니다. 근데 집에 피자치즈가 있어야 합니다." />
      <Article title="힘들었던 일주일이 드디어 끝" content="너무 빡센거같다. 야근도 적당히 해야지!!" />

      <Footer />
    </MouseMovementContext.Provider>
  );
};

이런 구조의 앱이 있다고 가정해봅시다.

MouseMovementContext라는게 있고 마우스가 움직이면 상태를 계속 업데이트합니다.

MouseMovementContext로 부터 마우스의 위치 상태 값을 받는 컴포넌트는 MousePositionMeter 밖에 없습니다.

과연 Header, Article, Footer 모두 리-렌더링 될까요?

네! 전부 리-렌더링 됩니다. App에서 setState를 함으로서 App에서 리-렌더링이 발생하는데, 함수에서 또 Header, Article, Footer를 호출하여 리-렌더링하는 식이기 때문에, 결과적으로 전체가 리-렌더링되고 있습니다.

내용물 끌어올리기

https://overreacted.io/ko/before-you-memo/#%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-2-%EB%82%B4%EC%9A%A9%EB%AC%BC%EC%9D%84-%EB%81%8C%EC%96%B4%EC%98%AC%EB%A6%AC%EA%B8%B0

내용물 끌어올리기에 대한 이해는 이 글이 많이 참고가 되었습니다. 꼭 읽어보시면 좋겠습니다.

const MouseMovementProvider = ({ children }: PropsWithChildren) => {
  const [mousePosition, setMousePosition] = useState<[number, number]>([0, 0]);

  // 마우스 움직임을 감지하여 mousePosition 상태 계속 업데이트
  useEffect(() => {
    document.addEventListener('mousemove', (event) => {
      setMousePosition([event.clientX, event.clientY]);
    });
  }, []);

  return (
    <MouseMovementContext.Provider value={mouseMovementContextValue}>
      {children}
    </MouseMovementContext.Provider>
  );
}

const App = () => {
  return (
    <MouseMovementProvider>
      <Header />

      <Article title="오늘도 즐거운 하루" content="오늘 새벽에 좀 많이 추워서 그런지 아침에 힘들었어요. 일교차가 크니 조심하도록 합시다.">
        <MousePositionMeter />
      </Article>
      <Article title="냉동피자가 4천원이면 꽤 괜찮은데?" content="피자치즈 얹어먹으면 더더욱 맛있습니다. 근데 집에 피자치즈가 있어야 합니다." />
      <Article title="힘들었던 일주일이 드디어 끝" content="너무 빡센거같다. 야근도 적당히 해야지!!" />

      <Footer />
    </MouseMovementProvider>
  );
};

내용물 끌어올리기를 적용하면 이와 같은 코드가 됩니다.

Header, Article, Footerchildren을 통해서 전달해주는데, 이 컴포넌트들은 App이 렌더링하여 MouseMovementProvider에 넘겨주기 때문에 App이 리-렌더링 되지 않는 이상 Header, Article, Footer은 리-렌더링 되지 않습니다.

MouseMovementProvider가 계속 리-렌더링되어도 Header, Article, Footer는 리-렌더링 되지 않습니다.

MousePositionMeter 에서는 useContext(MouseMovementContext) 를 사용하기 때문에 리-렌더링이 계속 되고 있네요.

  1. Context를 사용했을 때 전체가 리-렌더링 된다는 것과
  2. 전체 리-렌더링을 피할 수 있는 방법 (내용물 끌어올리기)

이렇게 알아보았습니다. React Context API를 쓰는데 있어 참고가 되면 좋겠습니다!

내용물 끌어올리기가 적용되지 않았을 때

마우스를 움직여 동작을 확인해보세요!

쓸데없는 일기 적는 앱
오늘도 즐거운 하루
오늘 새벽에 좀 많이 추워서 그런지 아침에 힘들었어요. 일교차가 크니 조심하도록 합시다.

YOUR MOUSE POSITION: [0, 0]

냉동피자가 4천원이면 꽤 괜찮은데?
피자치즈 얹어먹으면 더더욱 맛있습니다. 근데 집에 피자치즈가 있어야 합니다.
힘들었던 일주일이 드디어 끝
너무 빡센거같다. 야근도 적당히 해야지!!

YOUR MOUSE POSITION: [0, 0]

내용물 끌어올리기가 적용되었을 때

마우스를 움직여 동작을 확인해보세요!

쓸데없는 일기 적는 앱
오늘도 즐거운 하루
오늘 새벽에 좀 많이 추워서 그런지 아침에 힘들었어요. 일교차가 크니 조심하도록 합시다.

YOUR MOUSE POSITION: [0, 0]

냉동피자가 4천원이면 꽤 괜찮은데?
피자치즈 얹어먹으면 더더욱 맛있습니다. 근데 집에 피자치즈가 있어야 합니다.
힘들었던 일주일이 드디어 끝
너무 빡센거같다. 야근도 적당히 해야지!!

YOUR MOUSE POSITION: [0, 0]

전체 코드

주의: 코드를 대충 작성해서 더럽습니다... 깊게 이해하려 시도하지 마세요.

App.tsx
import React, { useState, useEffect, useMemo, useContext, forwardRef, useRef } from 'react';
import { PropsWithChildren } from 'react';
import './App.css';

const withDetectRender = <P,>(name: string, fc: React.FC<P>): React.FC<P> => {
  return (...args: Parameters<typeof fc>): ReturnType<typeof fc> => {
    const ref = useRef<HTMLDivElement>(null);
    console.log('render', name);

    const $div = ref.current;
    if ($div) {
      $div.classList.remove('rerender');
      $div.offsetHeight;
      $div.classList.add('rerender');
    }

    return (
      <div className="render-unit rerender" data-name={name} ref={ref}>
        {fc(...args)}
      </div>
    )
  };
}

const ArticleContent = withDetectRender('ArticleContent', (props: { content: string }) => {
  const {content} = props;

  return <p>{content}</p>;
});

const Article: React.FC<PropsWithChildren<{ title: string, content: string }>> = withDetectRender('Article', (props) => {
  const { title, content, children } = props;

  return (
    <article style={{ width: '600px' }}>
      <h1>{title}</h1>

      <ArticleContent content={content} />

      {children}
    </article>
  );
});

export const MouseMovementContext = React.createContext<{
  mousePosition: [number, number];
}>({
  mousePosition: [0, 0]
});

const MousePositionMeter = withDetectRender('MousePointerMeter', () => {
  const { mousePosition } = useContext(MouseMovementContext);

  return (
    <p>YOUR MOUSE POSITION: [{mousePosition.join(', ')}]</p>
  );
});

const Footer = withDetectRender('Footer', () => {
  return (
    <footer>
      <MousePositionMeter />
    </footer>
  );
});

const Button: React.FC<{ name: string }> = withDetectRender('Button', (props) => {
  const { name } = props;

  return (
    <button>{name}</button>
  );
})

const Header = withDetectRender('Header', () => {
  return (
    <header>
      <p>쓸데없는 일기 적는 앱</p>
      <ul style={{ display: 'flex' }}>
        <Button name="" />
        <Button name="글 목록" />
        <Button name="사이트 정보" />
      </ul>
    </header>
  )
})

const MouseMovementProvider: React.FC<PropsWithChildren> = withDetectRender('MouseMovementProvider', (props) => {
  const { children } = props;
  const [mousePosition, setMousePosition] = useState<[number, number]>([0, 0]);

  useEffect(() => {
    document.addEventListener('mousemove', (event) => setMousePosition([event.clientX, event.clientY]));
  }, []);

  const mouseMovementContextValue = useMemo(() => ({ mousePosition }), [mousePosition]);

  return (
    <MouseMovementContext.Provider value={mouseMovementContextValue}>
      {children}
    </MouseMovementContext.Provider>
  )
})

const App = withDetectRender('App', () => {
  return (
    <MouseMovementProvider>
      <Header />

      <Article title="오늘도 즐거운 하루" content="오늘 새벽에 좀 많이 추워서 그런지 아침에 힘들었어요. 일교차가 크니 조심하도록 합시다.">
        <MousePositionMeter />
      </Article>
      <Article title="냉동피자가 4천원이면 꽤 괜찮은데?" content="피자치즈 얹어먹으면 더더욱 맛있습니다. 근데 집에 피자치즈가 있어야 합니다." />
      <Article title="힘들었던 일주일이 드디어 끝" content="너무 빡센거같다. 야근도 적당히 해야지!!" />

      <Footer />
    </MouseMovementProvider>
  );
});

export default App;

Profile picture

Written by solo5star

안녕하세요 👋 개발과 IT에 관심이 많은 solo5star입니다

  • GitHub
  • Baekjoon
  • solved.ac
  • about
© 2023, Built with Gatsby