React Hook - useCallback

YEONGHUN KO·2022년 2월 24일
0

REACT JS - BASIC

목록 보기
18/31
post-thumbnail

useMemo에 이어서 useCallback이라는 훅에 대해서 설명해보려고한다. 사실 컨셉은 useMemo랑 거의 비슷하다. 차이점은 이름 그대로 콜백함수를 메모리에 저장한다는 뜻이다.

useMemo는 함수에서 리턴되는 값을 저장하는데 useCallback은 함수 자체를 저장한다.

그래서 useCallback을 통해 넘겨받은 함수에 인자를 넘겨주고 콜해서 리턴값을 받을 수 있다. useMemo때 썼던 앱을 그대로 활용해보자. 그러나 이번에는 컴포넌트에 함수를 넘겨주었다.

  // App.js
  const getItems = () => {
    console.log('getItems');
    return [number, number + 1, number + 2];
  };


  return (
    <div style={themeStyle}>
      <input
        type="number"
        value={number}
        onChange={e => setNumber(e.target.value)}
      />
      <button onClick={() => setDark(prevDark => !prevDark)}>
        Change Theme
      </button>
      <List getItems={getItems} />
    </div>
  );

// List.js
import { useEffect, useState } from 'react';

function List(props) {
  const { getItems } = props;
  const [num, setNum] = useState([]);
  

  useEffect(() => {
    console.log('useEffect inside useCallback.js');
    setNum(getItems());
  }, [getItems]);

  return <div>{num}</div>;
}

이때 App에 있는 button을 클릭하면 setDark가 실행되면서 리랜더링되고 그럼 getItems함수가 List컴포넌트에 넘어간다. 그리고 List컴포넌트에서는 useEffect에의해 useEffect 두번째 인자([dependencies])가 바뀔때만 getItems 함수가 새로 넘어간다.

근데 테마를 바꾸기만 해도 getItems함수가 새로 넘겨받은 것 처럼 인식하는 것이다. 그 이유는 역시나 App에서 랜더링 될때마다 새로운 getItems 함수를 넘겨주기 때문이다. 즉, getItems 함수의 reference가 매번 다른것이다.

그래서 useCallback으로 감싸주면 reference까지 같은 함수를 저장해놨다가 보내주기 때문에 useEffect에서도 같은함수로 인식해서 '테마가 바뀌는거면은' 더 이상 리랜더링하지 않게 된다.

그리고 useCallback은 함수를 리턴하기 때문에 getItems안에 인자를 넣고 함수를 호출할 수 도 있다.

  // App.js
  const getItems = useCallback((inc) => {
    console.log('getItems');
    return [number, number + inc, number + inc];
  },[number])


// List.js
  useEffect(() => {
    console.log('useEffect inside List.js');
    setNum(getItems(inc));
  }, [getItems]);

마지막으로 useMemo가 리턴하는 값이랑 useCallback이 리턴하는 값을 로그로 찍어서 비교해보자

useMemo가 리턴하는 값

 // App.js
  const getItems = useMemo(() => {
    console.log('getItems');
    return [number, number + 1, number + 2];
  },[number])

  console.log(getItems)

요런식으로 나온다. 리턴되는 값이 그대로 메모리에 저장된다 그리고 array가 저장되므로 List컴포넌트에서 호출할 때 당연히 에러가 뜰것이다.

useCallback이 리턴하는 값

 // App.js
  const getItems = useCallback(() => {
    console.log('getItems');
    return [number, number + 1, number + 2];
  },[number])

  console.log(getItems)

함수 그 자체가 넘어간다. 그리고 그 함수는 number가 바뀌지 않았을때는 이전 함수와 reference가 같은 함수가 넘어가기 때문에 List 컴포넌트에 있는 useEffect는 이전과 같은 함수로 인식해서 리랜더링 하지 않는다.

reference개념을 모르면 헷갈릴 수 있으니 원시변수와 객체가 메모리에 어떻게 저장되는지 알아두면 useCallback과 useMemo를 쉽게 이해할 수 있을 것이다.

덧붙여 말하자면, useMemo와 useCallback은 기존의 값을 그대로 전달해 주느냐 아님 새로 다시 만들어서 전달해주느냐를 결정하는 거지 랜더링의 횟수에 영향을 미치는 것은 아닌것 같다.

불필요한 랜더링을 줄일려면 React.memo나 context를 사용해야 할 것 같다.

다만 React.memo을 잘 활용하려면 useCallback을 이용해야한다. 함수나 객체가 prop으로 넘어간다고 할때 매번 랜더링될때 마다 새로운 ref 의 함수나 객체가 넘어가면 안되니 말이다.

언제 쓰는게 좋을까?

useCallback, useMemo는 얼핏보면 굉장히 좋아서 매번 써야할 것 같지만 아니다.

it depends 는 개발자들의 국룰이다. 여기서도 적용된다. 아래의 매번 쓰는게 아닌 아래의 두가지 경우에 해당되면 써야한다고 본다.

  1. 함수의 연산이 굉장히 복잡하고 방대할 때.
  2. ref를 isolate함으로써 동일성을 보장하고 싶을 때.

2번은 위에서 언급했으니 따로 설명안하겠다. 1번같은경우는 이런경우이다. 전교생의 데이터를 분석해서 언제 가장 집중이 잘되는지 통계를 내는 함수같은 경우이다.

랜더링 될때마다 이러한 함수를 만들고 연산하면 메모리, cpu연산 소모가 어마어마할 것이다. 그래서 이때는 memo해두는 것이 낫다.

그럼 끝!

profile
'과연 이게 최선일까?' 끊임없이 생각하기

0개의 댓글