useMemo

공룡개발자·2022년 4월 24일
0

컴포넌트 최적화를 위한 대표적인 hook 중 하나인 useMemo!

아래 코드는 어려운 계산과 쉬운 계산이 공존하는 코드이다.
어마무시한 for loop를 적용해 계산의 속도를 늦췄다.

문제는 쉬운 계산을 할 때 easyNumer라는 state가 업데이트되어 app 컴포넌트가 재랜더링되는데, 이는 함수가 재실행되는 것과 같아서 변수의 값들이 초기화됨을 의미한다.
결국 바뀌지 않은 hardSum이 다시 할당되면서 무시무시한 hardCalculate가 재실행된다.

import React, { useState } from 'react';

const hardCalculate = (number) => {
  console.log("어려운 계산");
  for (let i = 0; i < 999999999; i++) {} // thicking time!!
  return number + 10000;
}

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

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

  const hardSum = hardCalculate(hardNumber);
  const easySum = easyCalculate(easyNumber);

  return (
    <>
      <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> + 10000 = {easySum}</span>
    </>
  );
}

export default App;

그렇다면 바뀌지 않은 값은 재실행되지 않도록 할 수 있는 방법이 없을까? 즉, 바뀌기 전의 값을 메모리에 저장되어 꺼내쓰도록 구성할 수는 없을까?
이것이 useMemo이다!!

import React, { useMemo, useState } from 'react';

const hardCalculate = (number) => {
  console.log("어려운 계산");
  for (let i = 0; i < 999999999; i++) {} // thicking time!!
  return number + 10000;
}

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

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

  const hardSum = useMemo(() => {
    return hardCalculate(hardNumber);
  }, [hardNumber]);

  const easySum = easyCalculate(easyNumber);

  return (
    <>
      <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> + 10000 = {easySum}</span>
    </>
  );
}

export default App;

hardSum 변수에 useMemo를 할당해 hardNumber의 값이 변했을 때에만 연산이 되도록하고, 이외에는 캐싱된 값이 쓰일 수 있도록 설계하면 컴포넌트가 랜더링될 때 무의미한 연산을 막을 수 있어 최적화를 이뤄낼 수 있다!

그런데 사실 javascript 즉, 프론트에서 저렇게 1초씩이나 걸리는 연산을 할 일은 그리 많지는 않다. 다행히 useMemo는 연산보다 더 빛을 발하는 곳이 있는데, 객체타입을 변수에 할당할 때이다.

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

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

  const location = {
    country: isKorea ? "한국" : "외국"
  }

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

  return (
    <>
      <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>
    </>
  );
}

export default App;

변수에 원시타입이 아닌 객체타입을 할당하면 객체는 너무 크기 때문에 바로 변수에 할당되는 것이 아니라 메모리에 담기게 되고, 변수에는 그 메모리의 주소를 할당하게 된다. 그래서 같은 모습을 한 객체라도 비교연산자를 사용해 비교했을 때 false가 나오는 이유이다.
그렇다면 위의 상황도 number가 바뀔 때마다 app 컴포넌트는 리랜더링되는데, 그때 location이라는 변수에 객체가 재할당(주소가 바뀜)되기 때문에, useEffect의 의존성 배열에 위치한 location이 바뀐 것으로 인지해 겉으로는 location이 바뀌지 않았을 지라도 useEffect가 실행되게 된다.

import React, { useEffect, useMemo, useState } 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 (
    <>
      <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>
    </>
  );
}

export default App;

그래서 useMemo를 이용해 memoization하면 location이 바뀌지 않으면 기억된 객체가 사용되기 때문에 컴포넌트 최적화가 이뤄진다!!

profile
공룡의 발자취

0개의 댓글