Frontend/React

"React useMemo" : 메모이제이션을 통한 성능 최적화

개발작 2024. 2. 8. 18:53


1. 소개

  • React 앱을 개발하면서 성능 최적화는 항상 고려해야 할 중요한 측면임. 이때 useMemo 훅은 메모이제이션을 통해 성능을 향상시킬 수 있는 강력한 Hook임
  • 이번 글에서는 React의 useMemo 훅에 대해 알아보고, 그 특징과 장점에 대해 시작해보겠음

2. 특징

  • 메모이제이션: useMemo 훅은 이전에 계산된 값을 기억하여 동일한 계산을 반복하지 않고 이전에 계산된 값을 재사용하고,  이를 통해 불필요한 계산을 방지하여 성능을 향상시킴
  • 의존성 배열: useMemo 훅은 두 번째 매개변수로 의존성 배열을 받고, 이 배열에 포함된 값들이 변경될 때에만 메모이제이션된 값이 다시 계산됨

3. 장점

  • 성능 향상: useMemo를 사용하면 렌더링 시간을 줄일 수 있음! 특히 계산 비용이 높거나 자주 변경되지 않는 값들을 메모이제이션하여 불필요한 계산을 최소화할 수 있음
  • 코드 가독성: useMemo를 사용하면 계산된 값을 쉽게 추적할 수 있음, 이전에 계산된 값을 재사용하는 것이 명시적으로 표현되므로 코드의 의도를 명확하게 전달가능함
  • 렌더링 최적화: useMemo를 적절히 활용하면 불필요한 렌더링을 방지할 수 있고,  메모이제이션된 값이 변경되지 않는 한 해당 값이 사용된 컴포넌트는 다시 렌더링되지 않음

4. 예시코드

useMemo를 사용하기전 코드

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

const hardCalculate = (number) => {
  console.log("어려움 시간이 오래걸림 쓸데없는 반복을 돌기때문");
  // 스테이트에 값을 게속 넣고 비교하기때문에 렌더링을 반복문 만큼 게속하게되고
  // 즉각적으로 값을 변화시키는게 아니기 때문에 딜레이 값이 있음
  for (let i = 0; i < 9999999; i++) {
    return number + 10000;
  }
};

const easyCalculate = (number) => {
  // 마찬가지로 쉬운 계산기를 만들어도 App컴포넌트 안에 있어서
  // 전체적으로 렌더링이 되기때문에 hardCalculate도 동작을 해서
  // 쉬운계산기여도 딜레이를 가지게됨 이때 useMemo를 사용하면 좋음
  console.log("쉬움");
  return number + 1;
};

function App() {
  const [hardNumber, setHardNumber] = useState(1);
  const [easyNumber, setEasyNumber] = useState(1);
  const hardSum = hardCalculate(hardNumber);

  const easySum = easyCalculate(easyNumber);
  return (
    <div>
      <h3>어려운 계산기</h3>
      <input
        type="number"
        value={hardNumber}
        onChange={(e) => setHardNumber(e.target.value)}
      />
      <span> + 10000 = {hardSum}</span>

      <h3>쉬운 계산기</h3>
      <input
        type="number"
        value={easyNumber}
        onChange={(e) => setEasyNumber(e.target.value)}
      />
      <span> + 10000 = {easySum}</span>
    </div>
  );
}

export default App;

useMemo를 사용하기전

  • useMemo를 사용하기전에는 위와같이 불필요한 렌더링을 해서 hardCalculate, easyCalculate 함수를 실행하면 App 컴포넌트가 전체 렌더링이 되면서 불필요한값이 콘솔에 찍히게됨

2. useMemo를 사용후

  const hardSum = useMemo(() => {
    // use Memo는 두가지의 인자를 받음, 콜백함수, 의존성배열
    // 아래와같은 코드로 작성하면 hardNumber가 변경이 될때만
    // hardCalculate 함수가 돌기때문에 불필요한 렌더링을 방지할수있고
    // hardNumber가 변경되지않으면 이전에 가지고있던 값을 메모리에 저장해두었다가 사용함
    return hardCalculate(hardNumber);
  }, [hardNumber]);

  • useMemo를 사용한 후 에는 1번과 같은 불필요한 렌더링을 하지않고 사용하는 값만 렌더링하고, 의존성 배열안에 있는값이 변화하지 않으면 렌더링을 하지않고, 그전에 메모리에 저장된 값을 보여줌

5.  객체타입 사용시 useMemo

  • 원시타입 사용시 useEffect를 사용하면 아래 예시코드와 같이 해당된 원시타입의 변수에 대해서만 렌더링이 발생됨
  • 하지만 객체타입으로 useEffect 사용시 전체컴포넌트가 렌더링이 발생하는데 그 이유는 자바스크립트의 타입에 따라 렌더링이 되기 때문임, 원시타입은 변수 그대로의 값을 비교하지만 객체타입의 경우 객체안의 변수를 그대로 저장하는것이 아닌 메모리에 값이 할당이되고, 메모리의 주소가 객체안에 할딩이됨
  • 그래서 같은 원시타입을 비교하면 true가 나오지만
  • 같은 객체타입을 비교하면 메모리안에 할당된 주소가 다르기 때문에 같은변수처럼 보이더라도 false가 나오게됨
  • 이럴때 객체타입은 useMemo를 사용하여 불필요한 렌더링을 줄임
import React, { useEffect, useMemo, useState } from "react";

export default function App() {
  const [number, setNumber] = useState(0);

  const [isKoran, setKorea] = useState(true);

  const location = useMemo(() => {
    // 이렇게 useMemo를 사용하여 객체타입은 불필요한 렌더링을 방지하면
    // 햄최면 Input을 클릭해도 객체타입은 렌더링을 하지않게됨
    return {
      country: isKoran ? "한국" : "외국",
    };
  }, []);
  useEffect(() => {
    console.log("useEffect 호출함");
    // 의존성배열에 원시타입이아닌 객체타입이 들어가면
    // 햄최몇의 input값이 바뀌어도 렌더링이되어 콘솔이 찍히게됨

    // 그 이유는 자바스크립트가 동작하는 원리때문인데
    // 원시타입의 경우 값을 그대로 가져오지만
    // 객체타입의 경우 메모리값이 할당되고 그 메모리안에 할당이되고
    // 그객체안의 변수에는 메모리의 값이아닌 메모리의 주소가 할당됨

    //그예로 같은 원시데이터를 비교하면 true가 나오지만
    // 같은 객체타입을 비교하면 false가 나옴
    // 그이유는 객체안의 변수에는 메모리의 주소가 다르기때문임
  }, [location]);
  return (
    <div>
      <h2>햄최몇?</h2>
      <input
        value={number}
        type="number"
        onChange={(e) => {
          setNumber(e.target.value);
        }}
      />
      <hr />
      <h2>어디사냐?</h2>
      <p>여기살고 있는데 왜? 뭐? {location.country}</p>
      <button onClick={() => setKorea(!isKoran)}> 아니그냥</button>
    </div>
  );
}

6. 개념 추가

ㄱ. Call by value (값에 의한 호출):

  • 이 방식은 함수에 인자를 전달할 때 값 자체를 복사하여 전달합니다. 함수 내부에서 인자의 값이 변경되어도 호출된 쪽에는 영향을 주지 않습니다.
  • 호출하는 변수의 값만을 복사하여 함수 내부로 전달되기 때문에, 함수 내부에서 해당 값을 변경하더라도 호출된 곳의 변수 값에는 영향을 주지 않습니다.
  • 대부분의 언어에서 함수의 인자 전달 방식이 이 방식으로 이루어집니다.

ㄴ. Call by reference (참조에 의한 호출)

  • 이 방식은 함수에 인자를 전달할 때 변수의 참조(메모리 주소)를 전달합니다. 따라서 함수 내부에서 해당 참조를 통해 변수 값을 직접 변경할 수 있음.
  • 함수에 인자로 변수를 전달할 때, 변수의 메모리 주소가 전달되므로 함수 내에서 변수 값을 변경하면 호출한 곳에서도 변경된 값을 확인할 수 있습니다.
  • 이 방식은 몇몇 언어에서 사용되며, 일반적으로 포인터나 참조를 이용하여 구현됩니다.

'Frontend > React' 카테고리의 다른 글

"React에서의 상태 관리" : Custom Hook vs. Context API  (2) 2024.02.04