본문으로 바로가기

[React] useCallback, useMemo 잘 쓰기

category 기타 (+ Legacy)/Legacy 2022. 3. 11. 07:51

[React] useCallback, useMemo 잘 쓰기

React로 개발하다보면 useCallback, useMemo를 사용하는 경우가 많이 있습니다.

해당 hook들은 성능 개선을 위해서 사용됩니다.

조금 더 자세히 말하면 이 함수나 변수 값이 component 내부에서 특정 조건에 해당할 때만 다시 생성하겠다 라는 의미입니다.


해당 게시글은 useCallback, useMemo에 대해 상세하게 다루는 게시글이 아니니깐 여기까지만 설명하겠습니다.

그런데 위 useCallback, useMemo를 의도와 맞지 않게 사용하거나 사용하였는데 내가 원하는 대로 동작하지 않는 경우가 있습니다.

사실 의도와 맞지 않게 사용하는 것은 큰 문제가 되지 않지만, 내가 원하는 대로 동작하지 않는 것은 문제가 됩니다.

여기서 더 큰 문제는 개발자는 해당 동작이 자신이 의도한대로 동작하고 있다고 생각하는 것입니다.

왜 이러한 문제가 생기는 지 해당 게시글에서 알아보도록 하겠습니다.

(참고, 예시 코드는 useMemo만 다룰 것이고 useCallback은 동일하게 동작한다고 이해하시면 될 것 같습니다.)

useMemo

◆ 문제의 코드

const TempComponent = props => {
  const { count, onChange, numbers } = props;

  const objNumbers = useMemo(() => {
    console.log("하이 !");
    return Object.assign({}, numbers);
  }, [numbers]);

  return (
    <div>
      <span>{count}</span> <button onClick={onChange}>증가</button>
    </div>
  );
};

const App = () => {
  const [count, setCount] = useState(0);

  return (
    <TempComponent
      count={count}
      onChange={() => setCount(prev => prev + 1)}
      numbers={[1, 2, 3, 4, 5]}
    />
  );
};

export default App;

위와 같은 컴포넌트가 있다고 생각해봅시다.

예시를 위해 간단하게 만든 컴포넌트이므로 큰 동작은 하지 않습니다.

상위 컴포넌트에서 하위 컴포넌트로 state 변수, setState 함수, numbers 배열을 넘겨주었습니다.

TempComponent에서는 state 변수를 증가시킬 수 있도록 구현하였습니다.

그리고 objNumbers는 numbers 배열을 object로 만들었습니다.

numbers가 변하지 않으면 objNumbers가 다시 만들어지지 않도록 useMemo를 사용하였습니다.


◆ 코드의 문제점, 원인 및 해결 방법

개발자의 의도는 TempComponent에서는 1번만 useMemo를 통하고 나머지는 이후 기억되길 원할 것입니다.

다시 말하면 useMemo의 실행은 1번으로 생각합니다.

그러나 실제로 그럴까요? 증가 버튼을 클릭하게 되면 useMemo가 계속 호출되는 것을 볼 수 있습니다. 왜 그럴까요?

App 컴포넌트에서 state가 변경되기 때문에 렌더링이 일어납니다. 그렇게 되면 어떤 결과가 일어날까요?

TempComponent에 내린 numbers props도 다시 생성이 됩니다.

그렇기 때문에 useMemo에서는 numbers가 변경되었다고 판단하여 다시 기억하려고 useMemo를 실행합니다.

이해가 되었을까요? 그럼 어떻게 해결할 수 있을까요? 해결하는 방법은 아주 간단합니다. 아래 코드를 확인해봅시다.

const TempComponent = props => {
  const { count, onChange, numbers } = props;

  const objNumbers = useMemo(() => {
    console.log("하이 !");
    return Object.assign({}, numbers);
  }, [numbers]);

  return (
    <div>
      <span>{count}</span> <button onClick={onChange}>증가</button>
    </div>
  );
};

const numbers = [1, 2, 3, 4, 5];

const App = () => {
  const [count, setCount] = useState(0);

  return (
    <TempComponent
      count={count}
      onChange={() => setCount(prev => prev + 1)}
      numbers={numbers}
    />
  );
};

export default App;

useCallback

위 코드처럼 numbers를 react 컴포넌트 밖에 선언하면 됩니다.

그럼 해당 js 파일을 읽을 때 처음 생성하고 이후 변경이 일어나지 않겠죠?

이렇게 구현하면 개발자가 원하는 대로 딱 1번만 실행되고 그 이후는 useMemo에 의해 기억됩니다.

useCallback의 경우는 따로 코드와 함께 설명하지 않겠습니다.

useCallback도 useMemo와 동일하게 꼭 필요한 함수만 컴포넌트 안에서 작성하고,

컴포넌트 밖에서 작성해도 되는 함수들은 밖에서 작성하는 습관이 들이면 좋습니다.

굳이 컴포넌트 안에 넣고 성능 고도화 한다고 useCallback hook을 사용할 필요가 없습니다.

애초에 useCallback을 사용하지 않아야 할 함수에 사용한 거니 올바르지 않게 useCallback을 사용한 것이라고 볼 수 있습니다.

마지막

해당 내용은 틀릴 수도 있습니다. 틀린 내용이 있으면 조언 부탁드립니다.

반응형