본문 바로가기

공부/react

React 숙련 3 (Memoization)

반응형

Memoization 📖

리렌더링 발생 조건

  • 컴포넌트에서 state가 바뀌었을 때
  • 컴포넌트가 내려받은 props가 변경되었을 때
  • 부모 컴포넌트가 리렌더링 된 경우 자식 컴포넌트 모두

최적화

리액트에서 리렌더링이 자주 발생한다면 비용적인 측면에서 좋지 않기 때문에 최적화 작업이 필요합니다. 그래서 대표적인 방법으로 아래 3가지 방법이 있습니다.

 

`React.memo` : 컴포넌트를 캐싱

`useMemo` : 값을 캐싱

`useCallback` : 함수를 캐싱

 

React.memo📖

UI 트리

 

1번 컴포넌트가 리렌더링 될 때 2~7번이 모두 리렌더링 됩니다.

4번 컴포넌트가 리렌더링 될 때 6~7번이 모두 리렌더링 됩니다.

 

이 처럼 자식 컴포넌트에서 바뀐 게 없는데 리렌더링 되는 문제를 `React.memo`로 해결할 수 있습니다.

 

사용 방법

App.jsx

function App() {
  console.log("App 컴포넌트가 렌더링되었습니다!");

  const [count, setCount] = useState(0);

  // 1을 증가시키는 함수
  const onPlusButtonClickHandler = () => {
    setCount(count + 1);
  };

  // 1을 감소시키는 함수
  const onMinusButtonClickHandler = () => {
    setCount(count - 1);
  };

  return (
    <>
      <h3>카운트 예제입니다!</h3>
      <p>현재 카운트 : {count}</p>
      <button onClick={onPlusButtonClickHandler}>+</button>
      <button onClick={onMinusButtonClickHandler}>-</button>
      <div>
        <Box1 />
      </div>
    </>
  );
}

 

Box1.jsx

import React from "react";

function Box1() {
  console.log("Box1이 렌더링되었습니다.");
  return <div>Box1</div>;
}

export default React.memo(Box1);

 

 

정말 간단히 return 하는 컴포넌트를 `React.memo`로 감싸주면 됩니다. 부모 컴포넌트의 state 변경으로 인해 props가 변경이 일어나지 않는 한 컴포넌트를 리렌더링 되지 않습니다.

export default React.memo(Box1);

 

 

useCallback📖

React.memo는 컴포넌트를 메모이제이션 했다면, useCallback은 인자로 들어오는 함수 자체를 메모이제이션합니다.

 

 

만약 Box1 컴포넌트에서 `initCount`메서드를 props 받아와 초기화를 한다면 Box1 컴포넌트를 React.memo로 메모이제이션 해도 리렌더링이 발생하게 됩니다.

// App.jsx
const initCount = () => {
  setCount(0);
};

// Box1.jsx
function Box1({ initCount }) {
  console.log("Box1이 렌더링되었습니다.");

  const onInitButtonClickHandler = () => {
    initCount();
  };

  return (
    <div style={boxStyle}>
      <button onClick={onInitButtonClickHandler}>초기화</button>
    </div>
  );
}

 

왜 리렌더링이 발생할까요

 

함수도 객체의 한 종류이기 때문에 리렌더링이 발생하면 고유한 함수를 생성하기 때문에 주소값이 달라지고 이에 따라 하위 컴포넌트는 props가 변경됐다고 인식하게 되는 겁니다.

 

사용 방법

// 변경 전
const initCount = () => {
  setCount(0);
};

// 변경 후
const initCount = useCallback(() => {
  setCount(0);
}, []);

 

`initCount`를 특정 메모리 공간에 저장해 놓고(=메모이제이션) 의존성 배열에 값이 변경되지 않으면 동일한 함수를 props로 내려주게 됩니다. 그래서 리렌더링이 발생하지 않습니다.

 

useMemo 📖

`useMemo` 는 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 합니다.

 

간단하게 설명하면

 

처음 값을 계산해 메모리에 저장하고 필요할 때마다 다시 계산하지 않고 메모리에서 꺼내서 재사용합니다.

 

사용 방법

// as-is
const value = 반환할_함수();

// to-be
const value = useMemo(()=> {
  return 반환할_함수()
}, [dependencyArray]);

 

의존성 배열의 값이 변경될 때만 `반환할 함수()`가 호출됩니다.

 

import React, { useState, useMemo } from "react";

function HeavyButton() {
  const [count, setCount] = useState(0);

  const heavyWork = () => {
    for (let i = 0; i < 1000000000; i++) {}
    return 100;
  };

  // CASE 1 : useMemo를 사용하지 않았을 때
  // const value = heavyWork();

  // CASE 2 : useMemo를 사용했을 때
  const value = useMemo(() => heavyWork(), []);

  return (
    <>
      <p>나는 {value}을 가져오는 엄청 무거운 작업을 하는 컴포넌트야!</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        누르면 아래 count가 올라가요!
      </button>
      <br />
      {count}
    </>
  );
}

export default HeavyButton;

 

위 코드에서 `useMemo`를 사용하지 않고 버튼을 눌렀을 땐 화면이 리렌더링 되는 속도가 엄청 느리지만 `useMemo`를 사용하면 처음 값을 계산해 메모리에 저장하고 이후에는 메모리에서 꺼내서 재사용하기 때문에 값을 최적화를 할 수 있습니다.

 

주의 사항

메모이제이션을 남발하게 되면 별도의 메모리 공간 확보를 많이 하기 때문에 오히려 성능이 떨어질 수 있습니다.

필요할 때만 사용하는 것이 좋습니다!

 

 

반응형