본문 바로가기
Frontend/React

React memo 개념 및 사용 방법

by Forsaken Developer 2023. 1. 30.
728x90
728x90

React memo 개념 및 사용 방법

상위 컴포넌트로부터 하위 컴포넌트로 계속 뻗어나가는 컴포넌트 트리를 보면 한 가지 의문이 생긴다.

연결된 모든 컴포넌트 함수가 재실행되면 굉장히 많은 함수가 가상 비교가 된다는 것인데 성능에 영향을 미치지는 않을까 하는 질문 말이다.

이런 작업은 확실히 어느 정도의 성능을 필요로 하며 그런 식으로 모든 자식 컴포넌트를 재실행하는 것은 낭비다.

물론 가상 비교이기 때문에 리액트가 이런 식의 실행 및 비교 작업에 최적화되어 있어 간단한 어플리케이션에서는 전혀 문제가 되지 않지만 더 큰 어플리케이션이라면 좀 더 최적화가 필요하다.

따라서, 개발자는 특정한 상황일 경우에만 하위 컴포넌트를 재실행하도록 리액트에 지시할 수 있다.

이런 특정 상황은 컴포넌트가 받은 props가 변경된 경우를 예로 들 수 있다.

일단, props가 바뀌었는지 확인할 컴포넌트를 지정한 뒤에 이를 Wrapping한다.

React.memo는 인자로 들어간 이 컴포넌트에 어떤 props가 입력되는지 확인하고 입력되는 모든 props의 신규 값을 확인한 뒤 이를 기존의 props의 값과 비교하도록 리액트에게 전달한다.

그리고 props의 값이 바뀐 경우에만 컴포넌트를 재실행 및 재평가하게 된다.

부모 컴포넌트가 변경되었지만 그 컴포넌트의 props 값이 바뀌지 않았다면 컴포넌트 실행은 건너뛴다.

function App() {
  const [show, setShow] = useState(false);

  console.log('APP RUNNING');

  const toggleHandler = () => {
    setShow((prevShow) => !prevShow);
  };
 
  return (
    <div className="app">
      <Child show={show} />
      <Button onClick={toggleParagraphHandler}>Toggle</Button>
    </div>
  );
}

export default App;
const Child = (props) => {
  console.log('Child RUNNING');
  return <GrandChild>{props.show ? 'This is new!' : ''}</GrandChild>;
};

export default React.memo(DemoOutput);

이렇게 최적화가 가능하다면 왜 이걸 모든 컴포넌트에 적용하지 않는지 의문이 생길 수 있다.

최적화에는 비용이 따르기 때문이다.

memo 메소드는 부모 컴포넌트에 변경이 발생할 때마다 컴포넌트로 이동하여 기존 props 값과 새로운 값을 비교하는데 그러면 리액트가 두 가지 기존의 props 값을 저장할 공간이 필요하고 비교하는 작업도 필요하다.

컴포넌트를 재평가하는 데에 필요한 성능 비용과 props를 비교하는 성능 비용을 서로 맞바꾸는 것이다.

그리고 이는 props의 개수와 컴포넌트의 복잡도, 그리고 자식 컴포넌트의 숫자에 따라 달라지므로

어느 쪽의 비용이 더 높다고 말할 수 없다.

자식 컴포넌트가 많아서 컴포넌트 트리가 매우 크고 컴포넌트 트리의 상위에 위치해있다면 React.memo는 전체 컴포넌트 트리에 대한 쓸데없는 재렌더링을 막아 유용하다.

이와는 반대로 부모 컴포넌트를 매 번 재평가할 때마다 컴포넌트의 변화가 있거나 props의 값이 변화할 수 있는 경우라면 React.memo는 크게 의미를 갖지 못한다.

매우 작은 어플리케이션, 매우 작은 컴포넌트 트리의 경우에는 이런 과정을 추가하는 것이 필요가 없겠고 큰 규모의 어플리케이션은 그럴 만한 가치가 있다.

모든 컴포넌트를 React.memo로 래핑할 필요는 없다.

728x90
반응형

댓글