본문 바로가기
SPA/REACT

React memo를 사용하기 전에

by F.E.D 2022. 11. 13.

리액트를 사용함에 있어서 여러가지 performance 측면의 개선 방법에 대한 글들이 많습니다.

그 중에서 많이 사용되고 있는 React memo에 대한 이야기입니다.

 

리액트 메모를 사용하기 전에 체크해볼 수 있는 몇 가지 사항들은 다음과 같습니다.

 

  1. production 빌드를 실행 중인지 확인합니다. (개발 빌드는 매우 느립니다.)
  2. 상태를 렌더링 트리에서 불필요하게 상속받고 있는지 확인합니다.
  3. React DevTools Profiler를 실행해서 리렌더링 되는 부분을 확인해보세요. (Highlight 옵션을 통해서 확인 가능)
    만약에 비용이 비싸다면, 컴포넌트 자체를 memo()로 감쌉니다.
    그리고 필요한 함수에 useMemo도 추가합니다.

그 외에도, 심각하게 느린 컴포넌트 환경은 많습니다.

그 때, memo를 성급하게 사용하기 전에 정리할 수 있는 부분들이 있습니다.

다음과 같이 비용이 비싼 render tree가 있다고 가정 합니다.

 

import { useState } from "react";

export const App = () => {
  const [text, setText] = useState("");
  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <SlowComponent />
    </div>
  );
};

const SlowComponent = () => {
  const t1 = performance.now();
  while (performance.now() - t1 < 300) {}
  const t2 = performance.now();
  console.log(t2 - t1 + " milliseconds");
  return <p>아주 느린 컴포넌트</p>;
};

 

 

이 때 SlowComponent에 memo()를 사용해도 되겠지만, 사전 해결방법 두 가지를 제시합니다.

1. 상태를 아래로 내리기

렌더링 되는 트리를 살펴보면, 일부의 코드인 input만이 text와 연관되어있다는 사실을 알 수 있습니다.

그 부분을 <Input /> 컴포넌트로 추출하고, useState 연산을 자식 컴포넌트로 내립니다.

이제 text가 변경되면 Input 컴포넌트만 리렌더링 됩니다.

import { Input } from "./Input";

export const App = () => {
  return (
    <div>
      <Input />
      <SlowComponent />
    </div>
  );
};

const SlowComponent = () => {
  const t1 = performance.now();
  while (performance.now() - t1 < 300) {}
  const t2 = performance.now();
  console.log(t2 - t1 + " milliseconds");
  return <p>아주 느린 컴포넌트</p>;
};

 

2. 그렇다면 상태값을 부모에 적용할 때는 어떻게?

위의 방법은 아래에서 모든 컴포넌트의 상태가 결정될 때 유용하지만, 부모에 그 상태를 다시 할당해야할 경우에는 사용하기 힘듭니다.

그럴 때는, 컨텐츠 자체를 끌어올려서 사용하는 방법이 있습니다. children을 사용해서 유용하게 만들어보시죠.

 

import { useState } from "react";

export const App = () => {
  return (
    <ContentLiftUp>
      <SlowComponent />
    </ContentLiftUp>
  );
};

const SlowComponent = () => {
  const t1 = performance.now();
  while (performance.now() - t1 < 300) {}
  const t2 = performance.now();
  console.log(t2 - t1 + " milliseconds");
  return <p>아주 느린 컴포넌트</p>;
};

const ContentLiftUp = ({ children }) => {
  const [text, setText] = useState("");
  return (
    <>
      <div className="text_box">{text}</div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      {children}
    </>
  );
};

 

 

이제 ContentLiftUp의 관심사를 가지고 있는 컴포넌트 조각이 있고, 그 상태에서 children으로 비용이 비싼 컴포넌트를 감싸고 있어서, 함수 내의 연산이 서로 영향을 주지 않을 수 있게 되었습니다.

위의 방법은 여러 컴포넌트 이점을 가집니다.

서버 컴포넌트를 Hydrate 할 수 있는 준비가 되었다면, ContentLiftUp 컴포넌트는 children을 서버로부터 받을 수도 있습니다.

최상위의 React 상태 업데이트 되는 부분 조차도 클라이언트에서 이 부분을 건너 뛰면서 작용할 것입니다.

memo로도 이와 같은 일을 할 수는 없습니다.

그렇게 했음에도 충분하지 않을 경우에만 memo를 사용하는 것이 더 효율적일 것입니다 : )

리액트 합성 모델에 대해서 더욱 더 공부하면서, 많은 것들을 배워나가요!

 

출처 : https://overreacted.io/before-you-memo/

 

댓글