useMemo에 대해서

Kim Jason·2023년 3월 20일
0

React Hooks

목록 보기
1/3
post-thumbnail

useMemo

값에 대해 메모이제이션을 수행해 컴포넌트 렌더링 성능을 최적화 합니다.

🚪 들어가기 전에

원활한 이해를 위해 몇 가지 짚고 넘어가자.

함수형 컴포넌트

함수형 컴포넌트도 결국엔 함수다.
함수형 컴포넌트가 렌더링 되는 것은 함수를 호출하는 것과 같은 의미다.
함수를 호출하면 내부의 모든 변수가 초기화 된다는 당연한 사실을 되새겨보자.

useMemo 구조

const value = useMemo(() => {
  return calculate();
}, [item]);

useMemo는 2가지 인자로 '콜백 함수'와 '(의존성)배열'을 받는다.
첫 번째 인자인 콜백 함수는 메모이제이션 할 값을 리턴한다.
이 콜백 함수의 리턴값이 바로 useMemo가 리턴하는 값이다.
useMemo는 두 번째 인자인 배열에 명시된 값이 업데이트 될 때만 콜백함수가 실행된다.
콜백 함수가 다시 호출되면 기존 메모이제이션 된 값을 업데이트 한다.
참고로 배열이 비었다면 처음 메모이제이션 된 값만을 사용한다는 의미다.

언제 사용해야 할까?

컴포넌트 성능을 최적화 한다는 이유만으로 useMemo를 무분별하게 사용해서는 안 된다.
값을 메모이제이션 한다는 것은 값을 재활용 한다는 의미다.
값을 재활용 하기 위해서는 해당 값을 저장하기 위한 메모리가 소비된다.
따라서 불필요한 값들까지 메모이제이션 한다면 메모리 낭비가 발생한다.
useMemo의 무분별한 사용은 성능 악화로 이어질 가능성이 있다.

🧪 예제 1

문제

import { useState } from "react";

const calculate1 = num => {
  console.log("계산 1");
  for (let i = 0; i < 999999999; i++) {}
  return num + 10000;
};

const calculate2 = num => {
  console.log("계산 2");
  return num + 1;
};

function App() {
  const [number1, setNumber1] = useState(1);
  const [number2, setNumber2] = useState(1);

  const result1 = calculate1(number1);
  const result2 = calculate2(number2);

  return (
    <div>
      <h3>계산기 1</h3>
      <input
        type="number"
        value={number1}
        onChange={e => setNumber1(parseInt(e.target.value))}
      />
      <span> + 10000 = {result1}</span>

      <h3>계산기 2</h3>
      <input
        type="number"
        value={number2}
        onChange={e => setNumber2(parseInt(e.target.value))}
      />
      <span> + 1 = {result2}</span>
    </div>
  );
}

export default App;

number2 상태를 업데이트 하면 result2 값이 화면에 렌더링 될 때 지연이 발생한다.
그 이유는 number2 상태가 업데이트 되면 App 함수형 컴포넌트, 즉 App 함수가 실행되기 때문이다.
함수가 실행되면 내부 변수가 모두 초기화 되는데 이때 함수 calculate1의 복잡한 로직이 실행되면서 변수 result1 초기화 된다.
가만 생각해보니 뭔가 이상하다.
변수 result1과 변수 result2는 서로 독립인 관계다.
그런데 어느 한 쪽 때문에 다른 한 쪽이 영향을 받는다? 말이 안 된다!
이 문제를 해결하기 위해서는 어떡해야 할까?

해결 방법

const result1 = useMemo(() => calculate1(number1), [number1]);

이때 useMemo를 사용해서 문제를 해결할 수 있다.
기존 변수 result1에 할당된 값에 useMemo를 적용했다.
이제는 의존성 배열에 명시된 number1 상태가 변경될 때만 변수 result1이 업데이트 된다.
반대로 변수 result2에 대해서 useMemo를 적용하면 동일한 효과를 얻을 수 있다.

🧪 예제 2

문제

이번에는 원시 타입 값과 객체 타입 값의 경우에 대해 살펴보려 한다.
(변수 location에 주목하자)

import { useState, useEffect } from "react";

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

  const location = 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}</p>
      <button onClick={() => setIsKorea(prev => !prev)}>비행기 타기</button>
    </div>
  );
}

export default App;

변수 location은 '한국' 또는 '미국' 이라는 원시 타입의 값만 갖는다.
이때 버튼을 누르면 다음의 일들이 발생한다.

  • 버튼 클릭 > isKorea 상태 변경 > App 함수(컴포넌트) 재실행 > 변수 location 값 초기화 > useEffect 실행

반대로 number 상태를 업데이트하면 useEffect는 실행되지 않는다.
변수 location에 원시 타입의 값이 할당된 경우에는 두 개의 상태값이 독립 관계를 유지한다.

하지만 변수 location에 할당된 값을 객체 타입의 값으로 바꾸면 상황이 달라진다.

const location = {
    country: isKorea ? "한국" : "미국",
};

이때 버튼을 누르면 다음의 일들이 발생한다.

  • number 상태 변경(input에 값 입력) > App 함수(컴포넌트) 재실행 > 변수 location 값 초기화 > useEffect 실행

🤦🏻 number 상태가 업데이트 됐는데 왜 useEffect가 실행될까?
이유는 바로 변수 location에 할당된 값이 객체 타입이기 때문이다.
원시 타입과는 다르게 객체 타입의 값을 변수에 할당하게 되면 이전과는 다른 메모리 주소가 할당된다.
따라서 useEffect는 변수 location의 값이 바뀌었다고 인식하고 실행된다.
이 문제를 어떻게 해결할 수 있을까?

해결 방법

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

마찬가지로 useMemo를 사용해서 문제를 해결할 수 있다.
isKorea 상태가 업데이트 될 때만 useMemo 안 콜백함수가 실행된다.
다시 말해서 number 상태를 아무리 업데이트 해도 변수 location은 새롭게 초기화 되지 않는다.
그 결과 useEffect도 실행되지 않는다.
isKorea 상태와 number 상태가 독립인 관계를 유지할 수 있게 되는 것이다.

profile
성장지향형 프론트엔드 개발자

0개의 댓글