React - useCallback

ToastEggsToast·2021년 4월 4일
0

React

목록 보기
13/13
post-thumbnail

React Hooks

Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.

React의 Hooks는 리액트 버전 16.8 이후로 클래스 컴포넌트를 사용하지 않더라도 state와 다른 react의 기능들을 사용할 수 있도록 만들어졌습니다.

가장 기본적인 Hook에는 useState, useEffect, useContext가 있고, 그 외에는 useReducer, useCallback, useRef 등이 존재합니다.

오늘은 그 중에서도 useCallback에 대해 알아보고자 합니다.

UseCallback

useCallback과 인사를 하는 것은 이번이 처음은 아니다.
처음 입사를 하고 사수님이 살펴보라고 주셨던 코드에 useCallback이 늘 쓰여있어서 이건 뭘까..? 하고 생각했던 적이 있었다.
단순하게 메모이제이션(memoization)을 위한 것이라고 학습하긴 했지만, 그래서 이 훅이 언제 어떻게 사용 되어야하는가에 대해서는 아직도 알 수 없었다. 언젠간 좀 더 자세하게 학습하자 다짐했고, 그게 오늘이 기회가 될 것 같다 :)

memoization, 메모이제이션.

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

보통 메모이제이션을 설명하기 위해서는 피보나치 수열이나 팩토리얼을 생성하는 재귀 함수를 사용해 많이 설명하곤 하는데, 이번 경우에서도 팩토리얼 값을 구하는 것으로 예제를 들어보고자 합니다.
제로초님의 블로그를 참고하여 작성하였습니다.

팩토리얼(!) 은 넘겨받은 숫자 n을 기준으로 n x (n - 1) x (n - 2) x ... x 1까지의 곱한 값을 돌려줍니다.
예를 들어 5 팩토리얼은 5 x 4 x 3 x 2 x 1 = 120 이라는 결과를 도출합니다.

이 팩토리얼을 재귀함수를 통해 작성하면

const factorial = function(number) {
  if (number > 0) return number * factorial(number - 1);
  return 1;
};

위와 같은 방식으로 표현할 수 있을 것 입니다.
이 코드를 메모이제이션 기법을 활용해 이전 값을 저장할 수 있게 한다면, 반복되어 호출이 될 경우 맨 처음 호출하는 경우 보다 더 빠르게 값을 리턴 할 수 있게 됩니다.

const factorial = (function() {
  const save = {};
  const fact = function(number) {
    if (number > 0) {
      const saved = save[number - 1] || fact(number - 1);
      const result = number * saved;
      save[number] = result;
      console.log(saved, result);
      return result;
    } else {
      return 1;
    }
  };
  return fact;
})();

factorial(7); // 1 1, 1 2, 2 6, 6 24, 24 120, 120 720, 720 5040
factorial(7); // 720 5040-

처음에 실행 할 때에는 여러 번의 실행 과정을 거쳐야 했지만, 두 번째 실행에서는 이전 실행했던 값을 기억해 바로 결과값을 출력할 수 있게 됩니다.

useCallback 작성법

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

위의 함수는 리액트 공식 홈페이지에서 제시하는 useCallback 함수의 사용 예시 입니다.
배열에는 useEffect와 마찬가지로 dependency에 연관된 요소들을 전달하고, 이 종속성 요소들 중 하나가 변경 된 경우에만 변경된 콜백을 리턴합니다.
이를 통해 불필요한 랜더링을 방지하고, 참조 동등성에 의존하는 최적화된 하위 구성 요소에 콜백을 전달합니다.

사용 예시

어떤 컴포넌트에서 api를 호출하는 fetchData라는 함수를 작성했다고 가성해봅니다.

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

const Example = ({ dataId }) => {
  const [data, setData] = useState([]);
  
  const fetchData = () => {
    fetch(`https://example-api/users/${dataId}`)
      .then((res) => res.json())
      .then(({result}) => result);
  };
  
  useEffect(() => {
    fetchData().then((result) => setData(result));
  }, [fetchData]);
};

export default Example;

위의 컴포넌트는 dataId가 변경이 되지 않아도 계속해서 fetchData를 새롭게 불러와 계속해서 fetch를 진행하게 될 것입니다.

이러한 경우 최적화를 위해 사용할 수 있는 것이 useCallback 함수입니다.

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

const Example = ({ dataId }) => {
  const [data, setData] = useState([]);
  
  const fetchData = useCallback(() => {
    fetch(`https://example-api/users/${dataId}`)
      .then((res) => res.json())
      .then(({result}) => result);
  }, [dataId]);
  
  useEffect(() => {
    fetchData().then((result) => setData(result));
  }, [fetchData]);
};

export default Example;

위와 같이 useCallback의 디펜던시로 dataId를 설정해준다면, dataId가 변경되는 경우에만 fetchData에 인자로 넘겨진 함수(fetch)가 진행이 된다.

요약

단순 최적화를 위해 useCallback을 사용한다고 이해 할 때 보다, 어떤 경우 메모리의 낭비가 진행되고 그걸 방지하기 위해 어떤 방식으로 작동되는 훅을 사용해준다. 라고 이해를 하고 나니 어떠한 경우 useCallback을 사용해야 하는지 좀 더 구체화가 된 것 같습니다.

이전에는 단순이 모든 callback에 useCallback을 사용해주어야 하는 것인가? 라고 생각했었는데,
지금까지 찾아봐온 것을 참조하자면 어떠한 함수의 결과값이 컴포넌트의 데이터에 영향을 미쳐, 그 결과로 컴포넌트의 랜더링에 관여하게 되는 경우메모리의 낭비를 방지하고, 최적화된 랜더링을 제공하기 위해 사용해야 한다 라는 결론을 내릴 수 있었습니다.

아직까지 명확하게 이 경우! 라고 콕 찝어 설명할 수는 없겠지만, 지금까지 정리해온 내용을 바탕으로 컴포넌트를 작성하게 된다면 그 경계선이 이전보다 좀 더 뚜렷해지지 않을까 :)

profile
개발하는 반숙계란 / 하고싶은 공부를 합니다. 목적은 흥미입니다.

0개의 댓글