[최적화] - useMemo

Donggu(oo)·2023년 1월 20일
0

React

목록 보기
21/30
post-thumbnail

1. useMemo


  • useMemo는 리액트에서 을 memoization 할 수 있도록 해주는 함수다. useMemo는 두 가지 인자를 받는데, 첫 번째 인자는 콜백 함수이며 이 함수에서 리턴하는 값이 memoization 된다. 그리고 의존성 배열을 두 번째 인자로 받는다. 두 번째 인자에 빈 배열([])을 전달하면 첫 렌더링시에만 메모이제이션을 수행하고 이후에는 항상 메모이제이션 된 값을 꺼내와서 사용한다.
// useMemo(callbackFunction, deps]

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • memoization을 할 때 주의해야 할 점은 만약 새로운 값을 만들어서 사용해야 하는 상황임에도 불구하고 이전의 결과를 그대로 활용해버리면 버그가 발생할 수 있다.

  • 위의 예시에서 a, b 라는 두 가지 변수를 이용해서 메모이제이션 하기 위한 값을 계산하고 있다. 그런데 만약 a, b 라는 값이 변경되었는데 이전의 값을 그대로 활용해버리면 의도한 결과와 다른 결과가 나오게 될 것이다.

  • 이런 상황을 방지하기 위해 useMemo에서는 의존성 배열을 인자로 받아, 의존성 배열에 있는 값 중 하나라도 이전 렌더링과 비교했을 때 변경되었다면 메모된 값을 활용하는 것이 아니라 새로운 값을 다시 계산한다.

2. useMemo의 적용


  • 아래의 예시에서 delayCalculate 함수는 계산이 오래걸리는 함수이고, quickCalculate 함수는 계산이 빨리 끝나는 함수이다. "delayCalculate"의 숫자를 증가시켜보면 콘솔창에 찍히는데 딜레이가 걸리는 걸 볼 수 있다.

  • 그러나 계산이 빨리 끝나는 "quickCalculate"의 숫자를 증가시켜보아도 똑같이 딜레이가 걸리는 현상을 볼 수 있다. 이는 "quickCalculate"의 숫자 증가 시 quickNumber의 state가 변경되면서 컴포넌트가 리렌더링 되기 때문에 quickCalculate의 함수와 함께 delayCalculate 함수도 초기화 되면서 다시 호출되기 때문이다.

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

const delayCalculate = (num) => {
  console.log('delay..');
  // 복잡한 계산으로 가정하기 위해 일부터 딜레이를 주기 위한 for문
  for (let i = 0; i < 999999999; i++) { }
  return num + 10;
}

const quickCalculate = (num) => {
  console.log('quick!');
  return num + 1;
}

function App() {
  const [delayNumber, setDelayNumber] = useState(0);
  const [quickNumber, setQuickNumber] = useState(0);

  const delaySum = delayCalculate(delayNumber);
  const quickSum = quickCalculate(quickNumber);

  return (
    <>
      <div>delayCalculate</div>
      <input type='number'
        value={delayNumber}
        onChange={(e) => setDelayNumber(parseInt(e.target.value))} />
      <span> + 10 = {delaySum}</span>
      <div>quickCalculate</div>
      <input type='number'
        value={quickNumber}
        onChange={(e) => setQuickNumber(parseInt(e.target.value))} />
      <span> + 1 = {quickSum}</span>
    </>
  );
}

export default App;

  • 그러나 quickCalculate의 값이 변할 때 마다 delayCalculate의 값도 변할 필요가 없으므로, 이 값을 저장해두었다가 다시 꺼내 쓸 수 있다면 굳이 delayCalculate 함수를 호출할 필요도 없을 것이다.

  • 아래와 같이 useMemo로 호출하여 delayCalculate를 감싸주면, 이전에 구축된 렌더링과 새로이 구축되는 렌더링을 비교해 num값이 동일한 경우에는 이전 렌더링의 num 값을 그대로 재활용할 수 있게 된다. 그래서 "delayCalculate" 값 변경 시 여전히 딜레이가 있지만 "quickCalculate"의 값이 변할 때는 "delayCalculate"는 이전에 저장해둔 값을 꺼내오기 때문에 "delayCalculate"가 다시 호출되지 않고 딜레이도 안 걸리는 모습을 볼 수 있다.

  • 이는 메모이제이션(Memoization) 개념과 관계가 있다.

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

const delayCalculate = (num) => {
  console.log('delay..');
  for (let i = 0; i < 999999999; i++) { }
  return num + 10;
}

const quickCalculate = (num) => {
  console.log('quick!');
  return num + 1;
}

function App() {
  const [delayNumber, setDelayNumber] = useState(0);
  const [quickNumber, setQuickNumber] = useState(0);

  // useMemo로 감싸준 계산이 오래 걸리는 delayCalculate 함수
  const delaySum = useMemo(() => {
    return delayCalculate(delayNumber)
  }, [delayNumber]);

  const quickSum = quickCalculate(quickNumber);

  return (
    <>
      <div>delayCalculate</div>
      <input type='number'
        value={delayNumber}
        onChange={(e) => setDelayNumber(parseInt(e.target.value))} />
      <span> + 10 = {delaySum}</span>
      <div>quickCalculate</div>
      <input type='number'
        value={quickNumber}
        onChange={(e) => setQuickNumber(parseInt(e.target.value))} />
      <span> + 1 = {quickSum}</span>
    </>
  );
}

export default App;

Memoization

  • 메모이제이션(Memoization)은 알고리즘에서 자주 나오는 개념으로, 기존에 수행한 연산의 결과값을 메모리에 저장을 해두고, 동일한 입력이 들어오면 재활용하는 프로그래밍 기법을 말한다. 이 메모이제이션을 적절히 사용한다면 굳이 중복 연산을 할 필요가 없기 때문에 앱의 성능을 최적화할 수 있다.
  • useMemo는 이 개념을 이용하여 복잡한 연산의 중복을 피하고 React 앱의 성능을 최적화시킨다. 직접 메모이제이션 개념을 이용하여 로직을 구현할 수도 있으나, useMemo Hook을 호출하면 이런 로직을 직접 구현하는 것을 대신해주기 때문에 훨씬 간편하다.

0개의 댓글