본문 바로가기
리액트

[react] 불필요한 리렌더링 방지하기 - React.memo 성능 최적화

by jaewooojung 2019. 12. 23.

ReactJS


React.memo

리액트 애플리케이션에서는 state의 변화가 없는 컴포넌트다른 요인에 의해 리렌더링 되는 경우가 있습니다. 대표적으로 부모가 렌더링 되면서 함께 렌더링이 되는 경우입니다. 이런 경우에는 React의 memo 메서드로 해당 컴포넌트를 저장했다가 재사용할 수 있습니다(memoization). memo 메서드로 감싼 컴포넌트는 렌더링이 트리거 될 때 prop의 변화가 없는 경우 이전 렌더링 결과를 재사용합니다.

 

 

예시

App.js

import React, { useState } from "react";
import Item from "./Item";

const App = () => {
  const [numbers, setNumbers] = useState([1, 2, 3]);
  return (
    <div>
      {numbers.map(num => (
        <Item key={num} num={num}></Item>
      ))}
    </div>
  );
};

export default App;

 

 

Item.js

import React from "react";

const Item = ({ num }) => {
  return <div>{num}</div>;
};

export default Item;

 

numbers state은 숫자 배열이고, 각 요소는 Item 컴포넌트로 전달됩니다.

 

Item 컴포넌트에서 콘솔 출력을 통해 렌더링 결과를 확인해 보겠습니다.

import React from "react";

const Item = ({ num }) => {
  console.log(num); // 콘솔 확인
  return <div>{num}</div>;
};

export default Item;

출력화면

각 Item 컴포넌트가 렌더링 되면서 콘솔창에 num이 출력되었습니다.

 

이 상태에서 App 컴포넌트의 numbers state에 변경이 일어나면

const App = () => {
  const [numbers, setNumbers] = useState([1, 2, 3]);
  // 함수 생성
  const addNumber = () => {
    setNumbers(prev => [...prev, prev.length + 1]);
  };
  return (
    <div>
      {numbers.map(num => (
        <Item key={num} num={num} />
      ))}
      <button onClick={addNumber}>Click</button> // 함수 실행
    </div>
  );
};

state 변경 시 렌더링 결과 gif

 

Click을 누를 때마다 새로운 요소가 numbers에 추가되면서 모든 Item 컴포넌트가 리렌더링 됩니다. 새로 추가되는 컴포넌트의 경우 렌더링되는 것이 당연하지만, 기존에 이미 화면에 그려져 있는 컴포넌트는 불필요하게 리렌더링 되고 있습니다.

 

컴포넌트가 리렌더링 된다는 것은 컴포넌트의 자바스크립트가 모두 재실행되고, 이후 생성된 JSX가 HTML이 되어 브라우저의 출력 과정(레이아웃, 페인팅, 컴포지팅)을 거쳐 다시 화면에 그려지는 것을 의미합니다. 리액트에서 성능을 악화시키는 대표적인 요인입니다.
 
위와 같은 상황에서는 기존에 출력되어 있던 컴포넌트들은 리렌더링을 막아주는 것이 바람직합니다.
 
React.memo로 Item 컴포넌트를 감싸줍니다.

import React from "react";

const Item = ({ num }) => {
  console.log(num);
  return <div>{num}</div>;
};

export default React.memo(Item);

 

 

memo 메서드로 리렌더링을 방지하고자 하는 컴포넌트를 감싸주기만 하면 됩니다. memo는 기본적으로 swallow comparison으로 이전 props와 현재 props를 비교합니다. props에 변화가 없다면 부모로부터 렌더링이 트리거 되어도 무시하고 이전 렌더링 결과를 재사용합니다.

 

React.memo 사용 gif

 

새로 추가되는 컴포넌트는 렌더링 되고 있지만, 기존의 요소들은 이전과 같은 props(num)을 전달받기 때문에 memo에 의해 재사용되는 모습을 확인할 수 있습니다.

 

Deep comparison

memo는 기본적으로 shallow comparison을 수행하지만, 경우에 따라서 deep comparison을 수행하도록 구현할 수 있습니다. 두 번째 인자비교함수를 전달하면 됩니다. 비교함수가 true를 반환하면 이전의 값이 기억되어서 리렌더링을 막아주고 false를 반환하면 새로운 상태로 인식하고 해당 컴포넌트를 리렌더링 합니다.

 

위의 예시는 아래와 같은 비교함수를 전달한 것과 동일합니다.

import React from "react";

const Item = ({ num }) => {
  console.log(num);
  return <div>{num}</div>;
};

const equalComparison = (prevProps, nextProps) =>
  prevProps.num === nextProps.num;

export default React.memo(Item, equalComparison);

 

props 객체 안에 여러 가지 프로퍼티들을 받는 경우, 특정 프로퍼티만 고려하도록 구현할 수도 있습니다.

import React from "react";

const Item = ({ num, title }) => {
  return <div>{obj.num}</div>;
};

const equalComparison = (prevProps, nextProps) =>
  prevProps.title === nextProps.title;

export default React.memo(Item, equalComparison);


        
답변을 생성하고 있어요.