[React] useMemo

MiMi·2022년 4월 13일
2

📘React

목록 보기
5/6
post-thumbnail

useMemo

useMemo란?

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

메모이제이션된 값을 반환한다.

useMemo는 처음에 계산된 계산값을 메모리에 저장해서 컴포넌트가 반복적으로 렌더링이 되어도 이전에 계산된 값을 메모리에서 꺼내올 수 있다.
두 번째 인자 값인 배열이 바뀔 때마다 메모이제이션된 값을 꺼내오는데 배열이 빈 상태라면 맨 처음 컴포넌트가 마운트 되었을 때만 값을 계산하고 그 이후에는 그 계산된 값을 꺼내온다.

import React, { useMemo, useState } 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 Memo() {
  const [hardNumber, setHardNumber] = useState(1);
  const [easyNumber, setEasyNumber] = useState(1);
  const hardSum = useMemo(() => {
    return hardCalculate(hardNumber);
  }, [hardNumber]);
  const easySum = easyCalculate(easyNumber);
  return (
    <div>
      <h3>어려운 계산기</h3>
      <input
        type="number"
        value={hardNumber}
        onChange={(e) => setHardNumber(parseInt(e.target.value))}
      />
      <span> + 10000 = {hardSum}</span>
      <h3>쉬운 계산기</h3>
      <input
        type="number"
        value={easyNumber}
        onChange={(e) => setEasyNumber(parseInt(e.target.value))}
      />
      <span> + 1 = {easySum}</span>
    </div>
  );
}

위 코드에서 useMemo를 사용하지 않으면 쉬운 계산기를 눌렀을 때도 hardCalculate가 실행되기 때문에 1초의 지연 시간이 생긴다. 하지만 useMemo를 사용하게 되면, hardNumber가 바뀌었을 때만 함수를 실행하므로 쉬운 계산기는 제대로 동작한다.
실제 코드에서는 위와 같이 1초 이상 걸리는 함수로 컴포넌트 내부의 변수를 초기화하는 경우는 많지 않다. 따라서 useMemo가 빛을 발하는 경우는 아래와 같이 로직이 짜여있을 때이다.

import React, { useEffect, useState } from "react";

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

  const location = isKorea ? "한국" : "외국";
  useEffect(() => {
    console.log("useEffect 호출");
  }, [location]);
  return (
    <div>
      <h3>하루에 몇끼 드심?</h3>
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(e.target.value)}
      />
      <hr />
      <h3>지금 어디?</h3>
      <p>나라: {location}</p>
      <button onClick={() => setIsKorea(!isKorea)}>비행기 타고 가요~</button>
    </div>
  );
}

useEffect의 의존성 배열 안에 location을 넣어줬기 때문에 number를 바꿔도 console이 찍히지 않는다. 하지만 아래와 같이 location이 객체라면 number를 바꿔줘도 console이 찍히게 된다. 왜냐하면 객체는 원시타입이 아니라 객체타입이기 때문이다.

import React, { useEffect, useState } from "react";

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

  const location = { country: isKorea ? "한국" : "외국" };
  useEffect(() => {
    console.log("useEffect 호출");
  }, [location]);
  return (
    <div>
      <h3>하루에 몇끼 드심?</h3>
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(e.target.value)}
      />
      <hr />
      <h3>지금 어디?</h3>
      <p>나라: {location.country}</p>
      <button onClick={() => setIsKorea(!isKorea)}>비행기 타고 가요~</button>
    </div>
  );
}

잠깐! 여기서 원시(Primitive)타입과 객체(Object)타입이란?

원시타입에는 String, Number, Bigint, Boolean, Undefined, Symbol, 그리고 Null이 있고 객체타입에는 Object, Array가 있다. 원시타입의 값은 값이 바로 들어가지만 객체타입의 값은 크기가 크기 때문에 메모리 상에 공간이 할당되어 그 메모리 안에 보관이 되고, 변수 안에는 그 객체가 담긴 메모리가 할당이 된다.
따라서 원시타입은 같은 값이 들어있는 다른 변수를 비교하면 True이지만 객체는 다른 메모리에 할당되기 때문에 다른 주소를 비교하므로 False가 나온다. 그래서 앞의 코드에서 렌더링이 일어나면 새로운 location 객체의 메모리가 이전의 location이 참조하고 있는 메모리와 다르기 때문에 console이 찍히게 되는 것이다.
따라서 아래와 같이 코드를 바꿔줘야한다.

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

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

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

  useEffect(() => {
    console.log("useEffect 호출");
  }, [location]);
  return (
    <div>
      <h3>하루에 몇끼 드심?</h3>
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(e.target.value)}
      />
      <hr />
      <h3>지금 어디?</h3>
      <p>나라: {location.country}</p>
      <button onClick={() => setIsKorea(!isKorea)}>비행기 타고 가요~</button>
    </div>
  );
}

주의사항

useMemo로 전달된 함수는 렌더링 중에 실행되므로 사이트 이펙트같은 렌더링 중에 하지 않는 일은 useMemo안의 함수에서 처리하면 안된다.
useMemo는 메모리를 차지하기 때문에 꼭 필요한 경우에만 사용하는 게 좋다.

참고문헌

https://ko.reactjs.org/docs/hooks-reference.html#usememo
https://www.youtube.com/watch?v=e-CnI8Q5RY4
https://developer.mozilla.org/ko/docs/Web/JavaScript/Data_structures

profile
이제 막 입문한 코린이 -> 이전중 https://mimi98.tistory.com/

0개의 댓글