useMemo

2023-09-04
ReactHooks

Table of Contents

useMemo가 나오게 된 배경

  • 렌더링 시 계산 비용이 많이 들어가는 계산에 대해서 값을 재사용하고, 렌더링 시 실행을 방지해 성능을 최적화 하기 위해서 나온 Hook.

1. 프로젝트 세팅 React + TS + Vite

$ npm init vite@latest React-UseMemo -- --template react-ts
$ cd React-UseMemo
$ npm i

/src/App.tsx

import "./App.css";
import { useState } from "react";

function App() {
  // useState의 set 함수 호출로 updating 트리거
  const [render, setRender] = useState(false);
  // updating 과정마다 반복문 계속 실행.
  const value = calculate();

  function renderHandler(event: React.MouseEvent) {
    event.preventDefault();
    setRender(!render);
  }
  return (
    <div>
      {value}
      <button onClick={renderHandler}>render!</button>
    </div>
  );
}

function calculate() {
  for (let i = 0; i < 200; i++) {
    console.log(i);
  }
  return 10;
}
export default App;
  • 반복문이 컴포넌트가 rendering 될 때마다 실행되고 있습니다. 매번 200번의 반복문이 돌아가고 있습니다.

2. useMemo 사용해보기

useMemo의 구조

const cachedValue = useMemo(calculateValue, dependencies)
  • useMemo는 첫번째 파라미터로 저장할 값을 받고, 두번째 파라미터로 의존성 배열을 받습니다. 이 의존성 배열 안에 값이 바뀐다면 값을 다시 계산하고 할당합니다.
  • 의존성 배열에 빈값이 할당된다면, 첫 마운트 시에 계산된 값을 저장하고, 다시 실행하지 않습니다.
  • 의존성 배열에 값이 할당된다면, 첫 마운트 혹은 그 값이 변경되었을때 다시 할당합니다.

의존성 배열에 값이 없을 시

import "./App.css";
import { useState, useMemo } from "react";

function App() {
  // useState의 set 함수 호출로 updating 트리거
  const [render, setRender] = useState(false);
  // useMemo 추가로 반복문이 실행되지 않음
  const value = useMemo(() => {
    return calculate();
  }, []);
  // const value = calculate();
  function renderHandler(event: React.MouseEvent) {
    event.preventDefault();
    setRender(!render);
  }
  return (
    <div>
      {value}
      <button onClick={renderHandler}>render! {`${render}`}</button>
    </div>
  );
}
function calculate() {
  for (let i = 0; i < 200; i++) {
    console.log(i);
  }
  return 10;
}
export default App;
  • 첫 마운트 시에만 계산하고, 저장합니다.

의존성 배열에 값이 있을 시

import "./App.css";
import { useState, useMemo } from "react";

function App() {
  // useState의 set 함수 호출로 updating 트리거
  const [render, setRender] = useState(false);
  // render를 추가함에 따라 render로 useMemo가 초기화됨
  const value = useMemo(() => {
    return calculate();
  }, [render]);
  // const value = calculate();
  function renderHandler(event: React.MouseEvent) {
    event.preventDefault();
    setRender(!render);
  }
  return (
    <div>
      {value}
      <button onClick={renderHandler}>render! {`${render}`}</button>
    </div>
  );
}
function calculate() {
  for (let i = 0; i < 200; i++) {
    console.log(i);
  }
  return 10;
}
export default App;
  • 첫 마운트 시와 render의 값이 바뀔 시 계산하고, 저장합니다.

참조형 데이터 타입의 주소가 변하는 경우

  • 함수형 컴포넌트가 re-rendering되는 경우 참조형 타입의 경우 주소 값이 재할당이 되어, 참조형 데이터타입의 값이 실제로 바뀌지 않았는데도 바뀌었다고 인식하는 경우가 있습니다.
import "./App.css";
import { useState, useMemo, useEffect } from "react";

function App() {
  // useState의 set 함수 호출로 updating 트리거
  const [render, setRender] = useState(false);
  const [number, setNumber] = useState(0);

  const updating = {
    render: render ? true : false,
  };

  // updating 과정마다 반복문 계속 실행.
  const value = useMemo(() => {
    return calculate();
  }, [render]);

  // updating 구독, 리렌더링 시 계속 바뀌게 된다.
  useEffect(() => {
    console.log("updating ::: " + updating.render);
  }, [updating]);

  // const value = calculate();
  function renderHandler(event: React.MouseEvent) {
    event.preventDefault();
    setRender(!render);
  }
  // number를 증가하게 하는 handler
  function onClickHanlder(event: React.MouseEvent) {
    event.preventDefault();
    setNumber((prev) => prev + 1);
  }
  return (
    <div>
      {value}
      <button onClick={renderHandler}>render! {`${render}`}</button>
      <button onClick={onClickHanlder}>Number! {`${number}`}</button>
    </div>
  );
}
function calculate() {
  for (let i = 0; i < 200; i++) {
    console.log(i);
  }
  return 10;
}
export default App;
  • number state가 증가하게 되는 버튼을 누르면 useEffect는 updating 변수가 바뀌는 것을 감지하고 console.log를 출력하고 있습니다. 전혀 관계 없고, 동작하지 않은 변수가 다시 메모리 주소만 바뀌어 할당되고 있습니다.
 const updating = useMemo(() => {
    return {
      render: render ? true : false,
    };
  }, [render]);
  • updating 상수를 수정해보겠습니다. useMemo로 감싸고, render state를 의존성 배열에 넣겠습니다. 어떤가요? 실행되지 않습니다.

useMemo 주의사항

  1. useMemo 를 남용하지 말고, 계산 비용이 높은 작업에만 사용해야 합니다.
  2. 종속성 배열을 명확히 지정하여 어떤 값이 변경될 때 재계산할 지를 정확히 제어해야 합니다.
  3. useMemo 내에서 생성된 값은 불변성을 유지하고, 최신 데이터를 가져와서 사용하는 것이 중요합니다.