티스토리 뷰

반응형

최근 React를 활용한 웹사이트를 만드는 데, 급격한 스크롤을 할 시 성능이 급격하게 내려간 현상을 발견할 수 있었다. 마우스 관련 이벤트에 쓰로틀을 걸어도, 디바운스를 활용해도 성능이 개선되지 않았으나, useMemo를 활용하여 성능을 개선시킬 수 있었다. 그 컴포넌트는 무거운 연산을 하는데 컴포넌트가 다시 렌더링될때마다 다시 무거운 연산을 하기 때문이었던걸로 문제가 발생했기 때문이다. 

 

useMemo을 활용한 성능 차이를 알아보자.

thumbnail
useMemo에 대하여 알아보자.


1. useMemo

useMemo는 React에서 공식적으로 제공하는 hook의 일종으로, 컴포넌트에서 이미 계산된 값을 재사용할 시 활용가능한 함수이다. 즉, 만약 어떠한 이벤트 때문에 React가 컴포넌트를 리렌더링해야 하는 경우 useMemo를 활용하여 자원이 많이 드는 계산을 이전의 값을 활용하도록 강제할 수 있다. 이전의 값은 메모리 속에 메모이제이션되어 있다.

 

공식적인 설명은 아래와 같이 되어 있다.

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

내가 계산하고자 하는 값을 콜백 함수로 감싼 다음, React답게 두 번째 인자로 의존성 배열을 넣어주면 된다. 이 경우 useMemo로 감싼 함수는 이후 재렌더링시 메모이제이션 된 값을 재사용하게 된다. 즉, 컴포넌트 리렌더링 시 새로 계산할 필요가 없다는 이야기이다.

 

다시 함수 계산을 해야하는 경우는 의존성 배열을 참고한다. 동작 방법은 의존성 배열을 Object.is를 통해 비교하고, 만약 다를 경우 재계산을 수행한다. 따라서, 의존성 배열을 넘겨주지 않을 경우 항상 재계산을 수행하게 된다. 따라서, useMemo를 쓸 때는 반드시 의존성 배열을 넘겨주자.

 

다만, React는 useMemo를 활용한다고 모든 함수 재계산을 수행하지 않는 것은 아니다. useMemo로 감싼 함수가 만약 오랫동안 쓰이지 않았다면 자동으로 메모리를 해제하고, useMemo가 비효율적이라고 생각된다면 그 부분도 자동으로 메모리를 해제한다.

 

2. 성능 차이

실제 테스트 코드를 작성하여 useMemo 사용한 경우와 사용하지 않은 경우 어떤 차이가 있는지 알아보자. CRA로 React@18.2.0 버전 보일러플레이트를 활용하였고, 성능이 많이 요구되는 함수 하나를 임의로 넣었다.

import { useState } from "react";

const longComp = (val) => {
  console.time("long Comp");
  // Bottleneck
  for (let i = 0; i < 10 ** 9; i++) {
    val++;
  }
  console.timeEnd("long Comp");

  return val;
};

function App() {
  const [toggle, setToggle] = useState(0);
  const [count, setCount] = useState(0);

  const longCompValue = longComp(toggle);

  const toggled = () => {
    if (toggle === 0) {
      setToggle(1);
    } else {
      setToggle(0);
    }
  };

  return (
    <div className="App">
      <br />
      <button onClick={toggled}>Toggle</button>
      <br />
      <span>count : {count}</span>
      <br />
      <button
        onClick={() => {
          setCount((prev) => prev + 1);
        }}
      >
        increase count
      </button>
    </div>
  );
}

export default App;

위 코드를 실행하면 다음과 같다.

위에 Toggle을 눌렀을 경우에 당연히 고비용의 연산이 수행되지만, 이 경우 'increase count' 버튼만 눌러도 해당 컴포넌트가 재렌더링되면서 Toggle 관련 함수인 longComp까지 실행된다. 따라서, 실제 렌더링 성능은 기대하는 것보다 매우 낮아진다.

increase count 버튼을 3번 누르고, 크롬의 '성능(performance)' 탭을 통하여 실제 렌더링 시간과 여러 지표를 살펴보면 다음과 같다. 

useMemo 사용하지 않을 시

실제 지표를 살펴보면 각 버튼 클릭마다 4.5초가 평균적으로 걸렸으며, 따라서 불필요한 렌더링으로 인하여 성능이 확 낮아지는 것을 볼 수 있다. 


이제, useMemo를 사용하여 함수를 감싸보자. toggle에 따라 함수가 새로 계산되어야 하므로 의존성 배열에 toggle을 추가하고, useMemo를 활용하여 불필요한 렌더링을 줄여보자.

import { useMemo, useState } from "react";

...

function App() {
  const [toggle, setToggle] = useState(0);
  const [count, setCount] = useState(0);

  // const longCompValue = longComp(toggle);
  // Wrapped with useMemo
  const longCompValue = useMemo(() => longComp(toggle), [toggle]);
  ...
 }

이제, 똑같은 과정을 반복하여보자. increase count 버튼을 3번 누른 뒤 성능을 측정하였다.

useMemo를 사용할 시

useMemo를 사용할 경우 렌더링 시간이 1초도 걸리지 않았으며, 성능 차이가 확연히 나는 것을 볼 수 있다. 측정이 잘 되지 않을 만큼 렌더링 시간이 매우 짧은 것을 확인할 수 있다. 실제 toggle의 바뀌지 않을 때의 데이터가 메모이제이션되므로, 성능이 급격히 좋아진 모습을 확인할 수 있다. 다만, toggle 버튼을 눌렀을 때의 성능은 변하지 않는다. 의존성 배열로 toggle을 넣어놨기 때문이다. 실제로 리렌더링을 처리해야 하는 부분이기도 하다.

 

따라서, 메모리를 통해 성능을 더 끌여올려야 할 때  유용하게 사용할 수 있는 hook이다.

 

3. useMemo는 무적인가?

결론부터 말하자면 아니다. 뭐든지 하나의 성능이 끌어올려지면, 다른 하나의 성능은 내려가기 마련이다.

 

useMemo는 메모이제이션을 활용한다. 다이나믹 프로그래밍에서 활용하는 기법인데, 자주 사용하는 데이터를 '메모'해 놓음으로써 시간을 절약하는 기법이다. 시간을 줄일 수 있는 만큼, 메모리 사용량은 늘어나게 되고, React가 알아서 메모리를 해제하긴 하지만 무분별하게 낭비할 경우 메모리 점유 공간만 늘어날 가능성이 높다.

 

또한, useMemo를 활용할 정도의 연산을 직접 프론트엔드에서 처리할 일이 많지 않고, 고연산의 렌더링은 렌더링된 결과를 받아오거나 비동기로 처리하는 편이 더 좋은 방법이다. 또한, 고연산의 항목은 별도의 컴포넌트로 분리하여 다른 부분까지 재렌더링하지 않고 필요한 부분만 재렌더링을 하는 방법을 추천한다.

 

 

반응형
댓글
Total
Today
Yesterday
공지사항
최근에 올라온 글
최근에 달린 댓글
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함