[React] useMemo
본문 바로가기
프레임워크/React

[React] useMemo

by 은돌1113 2022. 1. 31.

컴포넌트 최적화를 위해 사용되는 대표적인 Hook에는 useMemo와 useCallback이 있습니다.

  [ useMemo란? ]  

useMemo에서 memo란 Momoization(메모이제이션)을 뜻합니다.

메모이제이션은 동일한 값을 return 하는 함수를 반복적으로 호출 해야 한다면 맨 처음 값을 계산 할 때

해당 값을 메모리에 저장 해서 필요할 때마다 또다시 계산하지 않고 메모리에서 꺼내서 재사용하는 기법을 말합니다.

 

즉, 자주 사용하는(필요한) 값을 맨 처음 계산 할 때 캐싱을 해두고

값이 필요할 때마다 다시 계산을 하는 게 아니라 캐시에서 꺼내서 사용한다는 것입니다.

 

이때 함수형 컴포넌트는 말 그대로 함수라는 사실을 기억 해야 합니다!

 

함수형 컴포넌트가 렌더링이 된다는 건 그 함수가 호출 된다는 것이고

함수는 호출 될 때마다 함수 내부에 정의되어 있는 모든 변수, 함수들이 초기화 됩니다.
(예외적으로 useState로 선언한 변수들은 컴포넌트가 사라지지 않는 이상 초기화 되지 않습니다.)

대부분의 리액트 컴포넌트는 state와 props의 변화로 인해 수많은 렌더링이 일어납니다.

컴포넌트가 렌더링이 될 때마다 value라는 변수가 초기화 되기 때문에 calculate 함수는 반복적으로 호출 됩니다.

만약 calculate 함수가 무거운 작업을 하는 함수라면 이는 매우 비효율적입니다!

 

useMemo를 사용하여 메모이제이션을 해주면 이런 상황을 간편하게 해결 할 수 있습니다.

 

useMemo는 처음에 계산된 결과값을 메모리에 저장해서 컴포넌트가 반복적으로 렌더링이 되어도

calculate 함수를 다시 호출하지 않고 이전에 이미 계산된 결과값을 메모리에서 꺼내와서 재사용 할 수 있게 해줍니다.

 

  [ useMemo 구조 ]  

useMemo는 두 개의 인자를 받습니다.

첫번째 인자로는 callBack 함수, 메모리제이션 해줄 값을 계산해서 return 해주는 함수


두번째 인자로는 의존성 배열을 받습니다.

배열 안에 요소의 값이 업데이트 될 때만 callBack 함수를 다시 호출해서

메모이제이션 된 값을 업데이트 해서 다시 메모이제이션 해줍니다.

만약 빈 배열을 넣어주면 처음 컴포넌트가 렌더링 되었을 때만 메모이제이션을 해줍니다.

이후에는 항상 메모이제이션된 값을 꺼내와서 사용합니다.

 

아무리 좋다고 하더라도 무분별 하게 남용하면 안좋습니다.

useMemo를 사용한다는 건 값을 재활용 하기 위해서 따로 메모리를 소비하여 저장 해놓는 다는 것입니다.

그렇기 때문에 불필요한 값들까지 모두 메모이제이션 해버리면 오히려 성능이 나빠 질 수 있습니다.


  [ 실습 ]  

 

첫번째 예제

더보기
import React, { useState, useMemo } from "react";

const hardCalculate = (number) => {
  console.log("어려운 계산!");
  for (let i = 0; i < 999999999; i++) {
    // 생각하는 시간
  }
  return number + 10000;
};

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

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

export default App;

이 경우 값을 바꿔 줄 때마다 1초씩 딜레이가 생기는 걸 볼 수 있습니다.

 

이번에는 쉬운 계산기를 만들어서 값을 변경 해보겠습니다!

예상 하기로는 쉬운 계산기에서는 딜레이 없이 값이 바로바로 변경 될 것 같지만

실제로는 그렇지 않습니다. 어려운 계산기처럼 1초의 딜레이가 생기죠!

 

왜냐하면 App 컴포넌트는 함수형 컴포넌트 이기 때문에

리렌더링이 일어날 경우 안에 있는 모든 변수, 함수가 초기화 됩니다.

 

그 말은 즉, hardCalculate 함수도 리렌더링이 일어날 때마다 호출 되기 때문에 딜레이가 생기게 됩니다.

이는 매우 비효율적이라는 생각이 듭니다.

 

그렇다면 이런 생각을 하게 됩니다.

easyCalculate를 호출 할 때는 hardCalculate를 호출 하지 않는 방법은 없을까??

→ useMemo를 사용하면 쉽게 해결 할 수 있습니다.

더보기
import React, { useState, useMemo } from "react";

const hardCalculate = (number) => {
  console.log("어려운 계산!");
  for (let i = 0; i < 999999999; i++) {
    // 생각하는 시간
  }
  return number + 10000;
};

const easyCalculate = (number) => {
  console.log("쉬운 계산!");
  return number + 1;
};

function App() {
  const [hardNumber, setHardNumber] = useState(1);
  const [easyNumber, setEasyNumber] = useState(1);
  const hardSum = useMemo(() => {
    hardCalculate(hardNumber);
  }, [hardNumber]);
  const easySum = easyCalculate(easyNumber);

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

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

export default App;

두번째 예제

 

위 영상에서 볼 수 있듯이 useEffect의 dependency array에 객체를 넣을 경우

number가 변경 될 때도 useEffect가 호출 되는 것을 볼 수 있습니다.

 

이를 해결하기 위해서 저는 두 가지 방법을 사용 해보았습니다.

 

1) useEffect에 dependency array에 요소를 구체적으로 명시하는 방법입니다.

 

2) useMemo를 사용하여서 location에 return 값을 메모이제이션 하는 방법입니다.

더보기
import React, { useState, useEffect, useMemo } from "react";

function App() {
  const [number, setNumber] = useState(0);
  const [isKorea, setIsKorea] = useState(true);

  const location = useMemo(() => {
    return { country: isKorea ? "한국" : "외국" };
  }, [isKorea]);

  useEffect(() => {
    console.log("useEffect 호출");
  }, [location]);

  return (
    <div>
      <h2>하루 몇끼 먹어요?</h2>
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(e.target.value)}
      />
      <hr />
      <h2>어느 나라에 있어요?</h2>
      <p>나라 : {location.country}</p>
      <button onClick={() => setIsKorea(!isKorea)}>비행기 타기</button>
    </div>
  );
}

export default App;

더 알아보기

 

'프레임워크 > React' 카테고리의 다른 글

[React] useReducer  (0) 2022.03.01
[React] useCallback  (0) 2022.02.18
[React] useEffect  (0) 2022.01.29
[React] useState  (0) 2022.01.29
[React] useContext + Context API  (0) 2022.01.26

댓글