[React] Hooks - useMemo, useCallback

eonisal·2023년 8월 6일
0

React

목록 보기
7/10
post-thumbnail

✈️ 성능 개선

리액트는 성능 최적화를 위한 Hook을 제공한다.

코드를 효율적이게 개선하는 방법은 여러가지가 있겠지만 이번에 소개할 useMemo와 useCallback 이라는 Hook은 '메모이제이션(Memoization)' 이라는 것을 활용하는 기술이다.

메모이제이션과 메모이제이션을 담당하는 두가지 훅에 대해 정리하며 리액트 컴포넌트의 성능을 개선하는 방법에 대해 알아보자.

메모이제이션(Memoization)

메모이제이션은 직역하자면 '메모리에 넣기' 정도의 의미이다.

컴퓨터가 어떠한 동작을 하는데 이 동작이 동일한 계산을 반복하는 부분이 많다면, 동일한 계산의 결과들을 어디 따로 저장해놓고 필요할때 그걸 바로 사용하는 것이 매번 같은 계산을 하느라 자원과 시간을 낭비하는것보다 훨씬 효율적인 방식일것이다.

이렇게 반복되는 계산 값을 메모리에 넣어두고 필요할때 가져다 써서 반복적인 연산을 빠르게 하여 프로그램의 실행속도를 높이는 성능 개선 방법이 메모이제이션이다.

메모이제이션(memoization)은 컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다.

출처 : 위키백과

리액트는 useMemo와 useCallback이라는 메모이제이션 담당 Hook을 제공한다.

📝 useMemo

useMemo는 메모이제이션된 값을 반환하는 함수로, 지정한 State나 Props가 변경될 경우 해당 값을 활용해 계산된 값을 메모이제이션하여 리렌더링 시 불필요한 연산을 줄여준다.

useMemo의 기본적인 사용법은 다음과 같다.

import { useMemo } from 'react';

const memoizedValue = useMemo(Callback, Deps);

useEffect와 같이 첫번째 인자로는 콜백함수, 두번째 인자로는 의존성 배열을 받는다.


리액트의 함수형 컴포넌트는 렌더링 시

컴포넌트 내부의 함수 호출 -> 컴포넌트 내부 변수 초기화

의 순서를 거친다. 이런 관점에서 볼때 다음의 예시를 보자.

const App = () => {
  const [value, setValue] = useState(1)

  const something = (() => {
        let sum = 0;
        for(let i = 0; i < 999999999; i++) {
            sum += i;
        }
        return sum;
    })()
    
    return (
        <div>
            <button onClick={() => {
                setValue((current) => {
                    return current + 1;
                })
            }}>
                {value}
            </button>
            <div>{something}</div>
        </div>
    )
}

이 컴포넌트가 렌더링되면 something 변수에 할당한 익명함수가 호출되고 something 변수가 그 함수의 리턴값으로 초기화 될것이다.

버튼을 눌러 state가 변하여 컴포넌트가 리렌더링 될때마다 이 익명함수가 실행되고 something 변수가 초기화되는 과정이 반복될 것이다.

이 익명함수처럼 실행시간이 굉장히 오래걸리는 함수라면 리렌더링 될때마다 딜레이가 걸리는 아주 비효율적인 컴포넌트가 되고 만다.

이럴때 useMemo를 이용하여 이런 반복적인 무거운 계산을 메모이제이션하여 성능을 개선할 수 있다.

위 코드를 useMemo를 사용하여 다시 작성해보자.

const App = () => {
  const [value, setValue] = useState(1)

  const something = useMemo(() => {
        let sum = 0;
        for(let i = 0; i < 999999999; i++) {
            sum += i;
        }
        return sum;
    }, []);
    
    return (
        <div>
            <button onClick={() => {
                setValue((current) => {
                    return current + 1;
                })
            }}>
                {value}
            </button>
            <div>{something}</div>
        </div>
    )
}

익명함수를 useMemo의 Callback에 넣어주고 Deps는 빈 배열로 지정해놓았다. 따라서 컴포넌트의 최초 렌더링 시 익명함수의 연산 결과가 메모리에 저장되게 된다.

익명함수를 한번은 실행해야 하니 최초 렌더링시에는 딜레이가 걸리지만 그 다음부터는 버튼을 눌러 컴포넌트가 리렌더링 되어도 딜레이없이 버튼 숫자가 증가하는 것을 확인할 수 있을 것이다.

이렇게 Deps를 빈 배열로 주어 컴포넌트가 처음 실행될때 함수를 한번 실행하고 그 결과를 메모이제이션 해둬서 이후의 실행부터는 그 값을 바로 꺼내 쓰는 방식으로 컴포넌트의 성능을 개선할 수 있다.

Deps에 요소를 지정해주면 그 요소인 state가 변할때만 useMemo의 Callback이 실행되어 메모이제이션된 값을 업데이트 해준다.

📖 useCallback

useMemo가 메모이제이션을 통해 특정 값을 재사용하기위한 함수였다면 useCallback은 메모이제이션을 통해 특정 함수를 재사용하기위한 함수다.

함수가 생성되는것 자체는 그렇게 자원을 많이 잡아먹는 작업은 아니지만, 얘도 useMemo와 마찬가지로 반복되는 내용을 굳이 매번 새로 할 필요는 없지않은가?

반복되는 함수가 컴포넌트 리렌더링때마다 불필요하게 다시 생성되는 것을 방지하여 성능을 조금이라도 더 개선하기 위해 사용한다고 보면 되겠다.

function App() {
  const [foo, setFoo] = useState(0);
  const [bar, setBar] = useState(0);
  const calc = useCallback(() => {
    return foo+bar;
  }, [foo, bar]);
  return (
    <div className="App">
      <input value={foo} onChange={e => {setFoo(parseInt(e.target.value))}} />
      <input value={bar} onChange={e => {setBar(parseInt(e.target.value))}} />
      <div>{calc()}</div>
    </div>
  );
}

input창 두개에 각각 숫자를 입력하면 두 수의 합을 보여주는 컴포넌트이다.

함수를 메모이제이션하는 것의 이점을 보여주기엔 매우 하찮은 예시지만 쓰는 이유 등은 useMemo와 거의 같기때문에 흐름만 이해하자 😢

input에 입력한 수의 합을 반환하는 함수를 useCallback의 콜백함수로 지정하였기 때문에 이 함수 자체가 메모이제이션된다.

그러면 컴포넌트가 리렌더링되어도 이 함수는 다시 생성되지 않고 메모이제이션해둔 함수를 그대로 갖다 쓴다.

물론 input에 숫자를 다시 입력하여 Deps 배열로 지정해둔 foo나 bar가 변하면 새로운 foo, bar를 더하는 함수로 메모이제이션이 업데이트된다.

⚠️ useMemo, useCallback 남용 금지!

useMemo, useCallback은 복잡하고 오래걸리는 무거운 작업들을 메모이제이션하여 UI의 성능을 개선해주는 유용한 도구지만 어쨌든 연산 결과나 함수를 메모리상에 넣어두는 것이기 때문에 많이 사용하면 메모리를 잡아먹어 성능을 오히려 저하시킬 수 있다.

따라서 연산이 매우 복잡한게 아님에도 남발하여 성능도 떨구고 유지보수도 어렵게 하지 말고 꼭 필요한 순간에만 적절히 사용할 수 있도록 하자.





참고자료

https://velog.io/@vvsogi/React-useMemo-useCallback%EC%9D%98-%EC%82%AC%EC%9A%A9-%EC%9D%B4%EC%9C%A0%EC%99%80-%EC%82%AC%EC%9A%A9%EB%B2%95
https://itprogramming119.tistory.com/entry/React-useMemo-%EC%82%AC%EC%9A%A9%EB%B2%95-%EB%B0%8F-%EC%98%88%EC%A0%9C
https://hayeondev.gatsbyjs.io/230202-usememo-and-usecallback/

profile
언제까지_이렇게_살아야돼_

0개의 댓글