surf react : hooks (useMemo, useCallback) - 3

🔔 Goal

  • react useMemo를 이해한다.
  • react useCallback을 이해한다.

useMemo와 useCallback 은 리액트의 렌더링 성능 최적화를 위한 hook 이기 때문에 필수적으로 사용할 필요는 없다.

하지만, 추후에 대규모 서비스를 참여하게 될 때 이러한 작은 성능이 그 서비스의 퀄리티를 확실하게 구분한다고 생각한다.

🌳 useMemo

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

useMemo의 첫번째 파라미터에는 어떻게 연산할지 정의하는 함수 넘기고 두번째 파라미터에는 deps 배열을 넣어주면 된다.

이 배열 안에 넣은 내용이 업데이트되면, 우리가 등록한 함수를 호출해서 값을 연산해주고, 만약에 내용이 바뀌지 않았다면 이전에 연산된 값을 재사용하게 된다.

배열이 없는 경우 매 렌더링 때마다 새 값을 계산하게 된다. 이를 통해 렌더링 시의 고비용 계산을 방지하게 해줄 수 있다.

또한, useMemo로 전달된 함수는 렌더링 중에 실행된다. 그렇기 때문에 렌더링 중에는 하지 않는 것을 이 함수 내에서 하지 말아야 한다.

예를 들어, 사이드 이펙트(side effects)는 useEffect에서 하는 일이지 useMemo에서 하는 일이 아니다.

side effects : API 호출, DOM 조작 등

아래 예시를 참고하면 이해하는데에 큰 도움이 될 것이다.
벨로퍼트님의 react.vlpt.us

공식 문서에는 아래와 같이 작성되어 있다.

useMemo는 성능 최적화를 위해 사용할 수는 있지만 의미상으로 보장이 있다고 생각하지는 마세요.
가까운 미래에 React에서는, 이전 메모이제이션된 값들의 일부를 “잊어버리고” 다음 렌더링 시에 그것들을 재계산하는 방향을 택할지도 모르겠습니다.
예를 들면, 오프스크린 컴포넌트의 메모리를 해제하는 등이 있을 수 있습니다.
useMemo를 사용하지 않고도 동작할 수 있도록 코드를 작성하고 그것을 추가하여 성능을 최적화하세요.

정말 필요할 때 사용하라고 하는 말인 것 같다.

🌳 useCallback

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

useMemo 와 비슷한 Hook 이다(useCallback은 useMemo를 기반으로 두고 있다). useMemo 는 특정 결과값을 재사용 할 때 사용하는 반면, useCallback 은 특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용한다.

useCallback(fn, deps)은 useMemo(() => fn, deps)와 같다.

컴포넌트 내에 선언된 함수들은 컴포넌트가 리렌더링 될 때마다 새로 만들어 진다. 함수를 선언하는 것 자체는 사실 메모리도, CPU 도 리소스를 많이 차지 하는 작업은 아니기 때문에 함수를 새로 선언한다고 해서 그 자체 만으로 큰 부하가 생길 일은 없다.

하지만 한번 만든 함수를 필요할 때만 새로 만들고 재사용하는 것은 여전히 소프트웨어 공학 측면에서 중요한 패러다임이다.

나중에 컴포넌트에서 props 가 바뀌지 않았으면 Virtual DOM에 새로 렌더링하는 것 조차 하지 않고 컴포넌트의 결과물을 재사용 하는 최적화 작업을 하는데, 함수를 재사용하는 것이 필수이다.

주의해야 할 점은, 함수 안에서 사용하는 state 혹은 props 가 있다면 꼭, deps 배열안에 포함시켜야 된다.

만약에 deps 배열 안에 함수에서 사용하는 값을 넣지 않게 된다면, 함수 내에서 해당 값들을 참조할때 가장 최신 값을 참조 할 것이라고 보장 할 수 없다.

props 로 받아온 함수가 있다면, 이 또한 deps 에 넣어주어야 한다.

위에 작성했다시피 useCallback 은 useMemo 를 기반으로 만들어졌다.

다만, 함수를 위해서 사용 할 때 더욱 편하게 해준 것 뿐이다. 아래처럼 useMemo로 콜백 함수를 반환하는 표현식을 작성할 수 있다.

const onToggle = useMemo(
  () => () => {
    /* ... */
  },
  [users]
);

useCallback 을 사용 함으로써, 바로 이뤄낼수 있는 눈에 띄는 최적화는 없다. 그 이유는 컴포넌트 렌더링 최적화 작업을 해주어야만 성능이 최적화되는데요,

아래 예시를 참고하면 이해하는데에 큰 도움이 될 것이다.
벨로퍼트님의 react.vlpt.us

🍃 React.memo와 함께 사용하기

useCallback() hook 함수는 자식 컴포넌트의 랜더링의 불필요한 렌더링을 줄이기 위해서 React.memo() 함수와도 사용할 수 있다.

React.memo() 함수로 감싼 컴포넌트 함수는 props 값이 변경되지 않는 한 다시 호출되지 않는다.

특정한 값 혹은 액션을 제어하는 handler 함수를 하위 컴포넌트에 props로 전달했다고 가정해보자.

  1. handler 함수를 useCallback에 담아 의존값이 변경되지 않으면 재사용할 수 있도록 했다.

  2. 컴포넌트가 리렌더링 된다.

  3. handler 함수의 의존값이 변경되지 않아 재사용되어 props로 넘긴다.

+ 추가로

위의 React.memo로 하위 컴포넌트를 감싸면 재사용된(변경되지 않은) props(handler)를 받았기 때문에 컴포넌트가 리렌더링되지 않는다.

이를 통해 불필요하게 함수나 하위 컴포넌트가 다시 렌더링되는 상황을 제거하여 최적화를 수행할 수 있다.

아래 예시를 참고하면 이해하는데에 큰 도움이 될 것이다.
벨로퍼트님의 react.vlpt.us

🍃 최적화의 늪에 빠지지마라

최적화라는 말에 React 컴포넌트 내에서 선언하는 모든 함수에 useCallback()를 무조건적으로 사용하는건 어리석은 짓이다.

일반적으로 소프트웨어의 성능 최적화에는 그에 상응하는 대가(코드가 복잡해지거나 유지보수가 어려워짐)가 따르며, 배보다 배꼽이 더 커질 수 있다. 아래처럼 말이다.

함수 재사용을 통해 절약한 비용 < 최적화 함수를 수행하는 비용

따라서, useCallback()를 사용하기 전에 실질적으로 얻을 수 있는 성능 이점이 어느 정도인지 반드시 직접 측정을 해보고 사용하는 것이 옳다.

Reference

좋은 웹페이지 즐겨찾기