frontend

React re-rendering 방지 디자인 패턴(4)

하리하링웹 2023. 1. 23. 18:17

React에서 re-rendering이 일어나는 조건은 크게 보면 3가지정도가 있다.

  1. 함수 내의 State가 변경되었을 때
  2. 부모 컴포넌트가 re-render되었을 때
  3. 컴포넌트의 Props값이 변경되었을 때

자세하게 설명하면 수많은 조건이 있겠지만 대부분의 re-rendering는 저 3가지 조건의 상호작용으로 일어나게 된다.

이번 글에서는 잘 응용하면 대부분의 경우 발생할 수 있는 re-rendering을 방지하는 방법에 대해 작성할 예정이다.

 

React의 memo에 대해 작성할 예정이다.

 

먼저 아래에 간단한 예시가 있다.

 

import { useState } from 'react';

import { Button } from '@/components/ui';

function LargeComponent() {
  console.log('rerender');
  return <div>Larrrrrrrge</div>;
}

export default function TestPage() {
  const [data, setData] = useState(0);

  return (
    <div>
      <h1>Data: {data}</h1>
      <Button onClick={() => setData((prev) => prev + 1)}>Increase</Button>
      <LargeComponent />
    </div>
  );
}

 

이는 Button을 클릭하여 data state가 변경될 때 마다 아래의 LargeComponent도 같이 re-rendering 되어버려 성능에 안좋은 영향을 미칠 수 있다.

re-render gif

 

이는 부모인 TestPage 함수가 data state에 의해 re-rendering이 발생하게 되면서 자식 컴포넌트인 LargeComponent도 re-render되기 때문에 발생하는 문제이다. 

 

이는 React의 memo라는 함수를 써서 간단하게 해결할 수 있다. 먼저 예시 코드를 작성해보면

 

import { memo, useState } from 'react';

import { Button } from '@/components/ui';

function LargeComponent() {
  console.log('rerender');
  return <div>Larrrrrrrge</div>;
}
const MemoizedLartgeComponent = memo(LargeComponent);

export default function TestPage() {
  const [data, setData] = useState(0);

  return (
    <div>
      <h1>Data: {data}</h1>
      <Button onClick={() => setData((prev) => prev + 1)}>Increase</Button>
      <MemoizedLartgeComponent />
    </div>
  );
}

 

위의 코드처럼 LargeComponent를 memo함수로 감싸주기만 하면 간단하게 필요없는 re-rendering을 해결할 수 있게 된다.

 

re-render X gif

 

memo의 역할은 매 re-render가 발생할 때 마다 여기에 감싸져있는 컴포넌트의 현재 Props와 이전 Props를 비교하여 만약 변경되지 않았다면 re-rendering 과정을 생략하는 역할을 한다.

 

이 때 주의할점은 이 Props들을 비교할 떄 얕은 비교를 한다는 점이다.

 

따라서 어떠한 객체가 넘어왔을 때 이에대해 주소값으로 비교를 하기때문에 함수같은것을 Props로 넘겨줄 경우에는 비교 결과 항상 다르다고 판단하여 매 번 re-rendering을 발생시키는 문제가 발생하게된다.

이러한 경우에는 비교를 하는 자원에 re-rendering까지 매 번 발생시키게되니 memo를 최악으로 사용하고 있다고도 말할 수 있다.

 

이를 방지하는 방법은 아래의 글에 적당히 설명을 해 놓았으니 참고하면 된다.

https://jjongsk.tistory.com/entry/React-re-rendering-%EB%B0%A9%EC%A7%80-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B43

 

React re-rendering 방지 디자인 패턴(3)

React에서 re-rendering이 일어나는 조건은 크게 보면 3가지정도가 있다. 함수 내의 State가 변경되었을 때 부모 컴포넌트가 re-render되었을 때 컴포넌트의 Props값이 변경되었을 때 자세하게 설명하면 수

jjongsk.tistory.com

 

memo에는 총 두 개의 인자가 있다. 첫 번째는 위에서 보이는 것과 같은 컴포넌트이고 두 번째는 사용자가 직접 커스텀하여 원하는 결과를 내보낼 수 있는 함수이다.

 

이 함수의 type은 아래와 같다.

(prevProps: Readonly<unknown>, nextProps: Readonly<unknown>) => boolean) | undefined

만약 이 함수의 return값이 true이면 re-rendering을 생략하고 false면 re-rendering을 진행하게 된다. 이를 적절히 응용하면 여러가지 방법으로 응용할 수 있다.

 

예를들어 

function LargeComponent({ onClick, data }: { onClick: () => void; data: number }) {
  console.log('rerender');
  return <div>Larrrrrrrge: {data}</div>;
}
const MemoizedLartgeComponent = memo(LargeComponent, ({ data: prevData }, { data: nextData }) => {
  return prevData === nextData;
});

 

위의 코드의 경우에는 onClick이 아무리 변경되더라도 두 번째 인자에서 prevData와 nextData값이 같다면 re-rendering이 발생하지 않게 된다.

 

import { memo, PropsWithChildren, useState } from 'react';

import { Button } from '@/components/ui';

function BigLayout({ children }: PropsWithChildren) {
  console.log('re-render');
  return <div>{children}</div>;
}
const MemoizedBigLayout = memo(BigLayout);

export default function TestPage() {
  const [data, setData] = useState(0);

  return (
    <div>
      <h1>Data: {data}</h1>
      <Button onClick={() => setData((prev) => prev + 1)}>Increase</Button>
      <MemoizedBigLayout>
        <p>Hello</p>
      </MemoizedBigLayout>
    </div>
  );
}

위의 경우에는 BigLayout이 memo로 감싸져 있다고 하더라도 children이 매 re-render마다 다르기 때문에 TestPage가 re-render될 때마다 BigLayout을 re-render하게된다.

 

이는 useMemo를 사용하는 방식으로 방지할 수 있지만

const MemoizedBigLayout = memo(BigLayout, () => true);

와 같은 방식으로 사용하여 아예 re-render를 방지하는 방식으로 사용해도 방지할 수 있다.

 

이 외에도 prevProps와 nextProps를 적절히 응용하여 필요한 부분만 비교를 하거나 하는 방식으로도 사용할 수 있다.

*대부분의 경우 useCallback, useMemo와 같은 것을 응용하는편이 버그 발생 가능성을 줄이고 더 보기 좋은 코드를 만들 수 있다.

 

 

memo는 사용하더라도 엄청나게 큰 메리트를 보지 못하는 경우가 많다. 

실제로 대부분의 웹 코드를 dev tool에서 분석해 보더라도 memo를 사용하고 있는 경우는 거의 없다고 볼 수 있다.

 

이는 주관적인 생각인데 프론트엔드라는 직군 특성상 이런 사소한 것들에서 오는 성능상의 문제들의 책임을 대부분 클라이언트에게 넘기기 때문에 발생하는 문제이며 프론트엔드에서 발생하는 큰 문제들은 백엔드에서 발생하는 큰 문제에 비해 상대적으로 매우 가벼운 이슈이기도 하기에 생기는 문제라고도 생각한다.

 

물론 이러한 것들이 잘못되었다고 생각하지는 않는다. 언제나 개발을 할때에 오버엔지니어링은 주의해야하며 프론트엔드 직군은 이 기준이 조금 더 낮을 뿐이라고 생각한다. 

 

memo를 포함하여 다른 re-rendering 방지 패턴들을 반드시 사용 할 필요는 없지만 개발을 할 때에 한 번 쯤은 생각을 하고 코스트를 비교해보며 사용해보는 것이 어떨까? 라는 생각을 하고 있다.

 

* memo는 re-rendering과정과 비교하였을 때 대부분의 경우에 성능적인 이득을 볼 수 있으므로 사용할 수 있는 곳에서는 무조건 사용하는것이 좋다고도 말할 수 있다. (다만 상대적으로 유지보수와 디버깅이 어려워질 수 있기 때문에 잘 판단해서 사용하는것을 추천함)

 

위 글에 대한 참고 자료