React 렌더링에 대한 깊은 이해 [6] - React.memo

최원빈·2022년 11월 21일
0

지난 글에서, props의 레퍼런스를 같게 유지해 자식 컴포넌트의 재렌더링을 피하는 방법을 배웠다.

좀 더 자세히 말하자면, 두 가지 방법을 통해 자식 컴포넌트의 렌더링을 피할 수 있었다.

첫째로, 만들어진 자식 컴포넌트의 인스턴스를 props로 넘김으로써 <Parent />의 렌더링이 <Child />의 렌더링과 연관이 없게 만들 수 있었다.

둘째로, children 이라는 특수한 props를 사용해 부모 컴포넌트가 자식 컴포넌트를 렌더링하는 것을 막아서 렌더링을 생략할 수 있었다.

결국 자식 컴포넌트의 렌더링을 막는다는 것은, 자식 컴포넌트에 "넌 렌더링되면 안 돼" 라고 정하는 것이 아닌, 부모 컴포넌트의 렌더링 과정동안 자식 컴포넌트를 만들지 않게 노력하는 행동인 것이다.


위 방법처럼 하라면 할 수야 있지만, 모든 컴포넌트를 저렇게 관리하라고 한다면...? 🤔
결국 렌더링을 한 계층 더 부모로 올라가서 관리하는 것과 다른 것이 없을 뿐더러, 분명히 흐름이 어색해진다.

부모 컴포넌트가 자식 컴포넌트를 호출하는 것은 아주 정상적인 흐름이다.
위 두 방법은 부모 컴포넌트가 자식 컴포넌트를 포함하긴 하지만, 호출하진 않는다.
기존의 흐름을 벗어나는 것은 분명한 약점이라고 생각한다.

이를 위해 React는 기존의 흐름을 유지하면서 렌더링을 최적화할 수 있도록 React.memo (이하 memo)라는 메소드를 제공한다.


React.memo()

컴포넌트가 동일한 props로 동일한 결과를 렌더링해낸다면, React.memo를 호출하고 결과를 메모이징(Memoizing)하도록 래핑하여 경우에 따라 성능 향상을 누릴 수 있습니다. 즉, React는 컴포넌트를 렌더링하지 않고 마지막으로 렌더링된 결과를 재사용합니다.

memo를 사용해 자식 컴포넌트를 감싸면, props를 비교하여 렌더링을 생략할 수 있다고 한다.

분명히 <Parent/>의 재렌더링은 <Child/>의 렌더링을 발생시켜야 하지만, props의 변화가 없기 때문에 렌더링을 생략한다.

여기서 중요한건 props의 비교 방법인데, bail out 과정처럼 동등 비교(oldProps === newProps)가 아니라는 점이다.

props가 갖는 복잡한 객체에 대하여 얕은 비교만을 수행하는 것이 기본 동작입니다. 다른 비교 동작을 원한다면, 두 번째 인자로 별도의 비교 함수를 제공하면 됩니다.

얕은 비교를 수행하기 때문에, 신경써야 할 부분들이 생긴다.


Props의 얕은 비교

얕은 비교란, 얕은 복사 방식으로 비교를 수행하는 방법이다.

// 간략한 실행 예시 https://github.com/dashed/shallowequal/blob/master/index.js
const shallowEqual(prevProps, nextProps){
  const prevKeys = Object.keys(prevProps);
  const nextKeys = Object.keys(nextProps);

  if (prevKeys.length !== nextKeys.length) {
    return false;
  }

  for (let idx = 0; idx < prevKeys.length; idx++) {
    const key = prevKeys[idx];

    if (!nextProps.hasOwnProperty(key)) {
      return false;
    }

    if(prevProps[key] !== nextProps[key]) {
      return false;
    }
  }
  
  return true;
}

props 객체 내부의 키를 순회하며, 각각의 키 값에 === 연산자를 통해 비교한다.
쉽게, objA.name === objB.name && objA.onClick === objB.onClick && ... 를 모든 키에 반복하는 과정이다.

그렇기에, memo로 메모이제이션 한 자식컴포넌트는 재렌더링을 막고 싶다면 이전의 props와 일치한 변수를 렌더링마다 가질 수 있도록 유지해야한다.

여기서 또 일치연산자(===)를 사용하기에, bail out 과정에서 마주쳤던 문제가 또 발생할 것이다.
함수를 포함한 모든 객체는 참조가 같지 않다면 일치연산자에서 false를 반환한다.

React는 기본적으로 컴포넌트 내부에서 정의한 함수와 객체를 렌더링마다 새로 정의하기 때문에 props로 그러한 함수나 객체를 넘겼다면 메모이제이션은 동작하지 않는다.

컴포넌트의 렌더링은 컴포넌트 함수를 호출해 JSX를 반환받는 과정이고, JSX를 반환하기까지 내부 로직을 처음부터 실행하므로 그 과정에서 정의되는 함수와 객체는 매 렌더링마다 새로 정의된다.

그렇다고 렌더링을 최적화하기 위해 모든 props를 원시 타입 값들로만 전달할 수도 없는 노릇이다.
다음 글에서는 메모이제이션에 대해 자세히 알아볼 예정이다.

profile
FrontEnd Developer

2개의 댓글

comment-user-thumbnail
2024년 2월 13일

정말 좋은 글 감사합니다.
1페이지부터 6페이지까지 하나도 안 놓치고 꼼꼼하게 잘 읽었습니다.
리액트의 렌더링에 대해 한층 더 깊이 이해했습니다.
감사합니다.

1개의 답글