240818 React 메모이제이션 - memo, useCallback

신한별·2024년 8월 17일

💪 매일 기록

목록 보기
58/97
post-thumbnail

메모이제이션 (Memoization)

01. 시작하기에 앞서

👽 memoization 의 개념은 왜 등장했나? 최적화 관점에서 살펴보기 렛츠고

(1) 리랜더링 발생 조건

리랜더링이 언제 발생한다고 생각하나?

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

재사용`하는 것


(2) 최적화

불필요한 랜더링을 줄여주고 최적화(Optimization) 하는 방법이 바로바로~

memo(React.memo) : 컴포넌트를 캐싱
useCallback : 함수를 캐싱
useMemo : 값을 캐싱

캐싱이란?

  • 은 이전에 계산된 값이나 생성된 객체/함수를 메모리에 저장해 두고, 필요할 때마다 다시 계산하거나 생성하지 않고 저장된 값을 재사용하는 것

02. memo (React.memo)

React.memo를 통해 컴포넌트를 memoization 하는 방법을 알아보자.

(1) memo란?

리랜더링 조건 중 하나, 부모가 리랜더링되면 자식도 리랜더링 된다. 이 때 자식은 '난 한 게 없는데 왜 ㅠㅠ' 라는 생각이 들 것이다. 이 때 자식 편을 들어주는 것이 React.memo

(2) 코드를 통해 문제상황 살펴보기

예제 코드와 함께 레츠고

App.jsx

import React, { useState } from "react";
import Box1 from "./components/Box1";
import Box2 from "./components/Box2";
import Box3 from "./components/Box3";

const boxesStyle = {
  display: "flex",
  marginTop: "10px",
};

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 style={boxesStyle}>
        <Box1 />
        <Box2 />
        <Box3 />
      </div>
    </>
  );
}

export default App;

Box1.jsx

import React from "react";

const boxStyle = {
  width: "100px",
  height: "100px",
  backgroundColor: "#91c49f",
  color: "white",

  // 가운데 정렬 3종세트
  display: "flex",
  justifyContent: "center",
  alignItems: "center",
};

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

export default Box1;

Box2.jsx

import React from "react";

const boxStyle = {
  width: "100px",
  height: "100px",
  backgroundColor: "#4e93ed",
  color: "white",

  // 가운데 정렬 3종세트
  display: "flex",
  justifyContent: "center",
  alignItems: "center",
};

function Box2() {
  console.log("Box2가 렌더링되었습니다.");
  return <div style={boxStyle}>Box2</div>;
}

export default Box2;

Box3.jsx

import React from "react";

const boxStyle = {
  width: "100px",
  height: "100px",
  backgroundColor: "#c491be",
  color: "white",

  // 가운데 정렬 3종세트
  display: "flex",
  justifyContent: "center",
  alignItems: "center",
};

function Box3() {
  console.log("Box3가 렌더링되었습니다.");
  return <div style={boxStyle}>Box3</div>;
}

export default Box3;

난 버튼 한 번 눌렀을 뿐인데 콘솔창이 난리나는 사태가 벌어진다. 😮

실제로 변한 것은 부모 컴포넌트인 App.jsx 뿐인데 나머지 자식들까지 리랜더링 되고 난리가 났다. 💥

memo를 통해 해결 할 수 있다!! 레츠고

(3) memo를 통해 해결

React.memo를 이용해서 컴포넌트를 메모리에 저장해두고 필요할 때 갖다 쓴다.
📌 이것이 바로 컴포넌트 memoization

한 번 해보자 레츠고

Box1.jsx, Box2.jsx , Box.jsx 전부 동일

export default React.memo(Box1);
export default React.memo(Box2);
export default React.memo(Box3);

이렇게 하면 버튼을 9번이나 눌러도 자식들은 가만히 있는다!!

📌 최초 랜더링 이외에는 App.jsx 컴포넌트의 state가 변경되더라도 자식 컴포넌트들은 랜더링이 되지 않는다! (아이들이 울지도 않고 잘 있네요. 👶)

03. useCallback

useCallback hook을 통해 함수를 memoization 하는 방법을 알아보자 레츠고

(1) useCallback 이란?

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

(2) useCallback의 필요성 (feat.예제)

Box1이 count를 초기화 해주는 코드라고 가정한다.

App.jsx

...

	// count를 초기화해주는 함수
  const initCount = () => {
    setCount(0);
  };

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

...

Box1.jsx

...

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

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

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

...

다시 난리가 난다. 💥

+ 버튼이나, - 버튼을 누를 때 그리고 초기화 버튼을 누를 때 모두
App 컴포넌트와 Box1 컴포넌트가 리렌더링 되는 것이 확인된다.

?? : React.memo를 통해서 Box1.jsx는 메모이제이션을 했는데도 리랜더링이 되는데 이게 말이 되나요?

🧐 네, 아가씨. 그것은 우리가 함수형 컴포넌트를 사용하기 때문이고 App.jsx가 리랜더링 되면서 코드가 다시 만들어지기 때문이죠.

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

📌 자바스크립트에서 함수도 객체의 한 종류다.
따라서 모양은 같더라도 다시 만들어지면 그 주소값이 달라지고,
이에 따라 하위 컴포넌트인 Box1.jsx는 props가 변경됐다고 인식 한다.

💥 그래서 우린 useCallback이 필요하다. 그를 불러보자 레츠고.

(3) useCallback 사용을 통한 함수 메모이제이션

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

이 문제의 함수를 메모리 공간에 가둬 저장해놓고 특정 조건이 아닌 경우엔 변경되지 않도록 한다.

useCallback 의 적용

App.jsx

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

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

이렇게 하니 다시 자식이 조용해졌다. 🤗

(4) 좀 더 나아가기

1개의 댓글

comment-user-thumbnail
2025년 10월 22일

감사합니다 ! 이해가 쏘옥 되었듭니당

답글 달기