React - useCallback

uk·2023년 2월 26일
0

React

목록 보기
9/17

useCallback이란?

useCallback은 컴포넌트의 성능을 최적하기 위해 메모이제이션 기법을 사용하는 React Hook이며 useMemo는 값을 재사용하기 위해 사용한다면 useCallback은 함수를 재사용하기 위해 사용한다.

const Calculator = ({ x, y }) => {
  const plus = () => x + y;

  return (
    <>
      <div>
	    {plus}
      </div>
    </>
  );
}

Calculator 컴포넌트 내에 plus 함수는 props로 넘어온 x, y 값을 더해 <div> 태그에 출력하고 있다. 이 함수는 Calculator 컴포넌트가 렌더링 될 때마다 호출될 것이다.

이때 useMemo와 마찬가지로 컴포넌트가 리렌더링 되더라도 함수가 의존하고 있는 값이(x, y) 바뀌지 않는다면 함수 또한 메모리에 저장해두고 재사용 할 수 있다.

useCallback을 사용하면 해당 함수가 의존하는 값들이 바뀌지 않는 한 기존 함수를 계속해서 반환한다. 즉, x, y값이 동일하다면 다음 렌더링 시 이 함수를 재사용할 수 있다.

import { useCallback } from 'react';

const Calculator = ({ x, y }) =>{
  const plus = useCallback(() => x + y, [x, y]);

  return (
    <>
      <div>
	    {plus}
      </div>
    </>
  );
}

하지만 useCallback만 사용해서는 useMemo만큼 최적화를 느낄 수 없다. useCallback은 그저 메모리 어딘가에서 함수를 꺼내 호출하는 Hook이기 때문이다.

단순히 컴포넌트 내에서 함수를 반복 생성하지 않기 위해 useCallback을 사용하는 것은 큰 의미가 없거나 오히려 손해인 경우도 있다. useCallback은 자식 컴포넌트의 props로 함수를 전달할 경우 사용하는 것이 좋다.

useCallback과 참조 동등성

useCallback은 참조 동등성에 의존한다. React는 JavaScript 기반의 라이브러리이기 때문에 기본적으로 JavaScript의 문법을 따른다.

JavaScript에서 함수는 객체이다. 객체는 메모리에 저장될 때 값이 저장되는 것이 아닌 값을 가리키는 주소가 저장되기 때문에 반환하는 값 자체는 같더라도 일치연산자(===)로 비교해보면 false가 출력된다.

const double = x => x * 2;
  
const func1 = double;
const func2 = double;
  
func1(5);  // 10
func2(5);  // 10

func1 === func2  // false

func1과 func2에 동일한 함수를 할당했음에도 함수가 저장된 메모리 주소 값이 다르기 때문에 메모리 주소에 의한 참조 비교 시 다른 함수로 판단한다.

이는 React 또한 같다. React는 리렌더링 시 함수를 다시 생성하게 되는데 새로 생성된 함수는 기존의 함수와 같은 함수가 아니다. 그러나 useCallback을 사용해 함수 자체를 메모리에 저장하고 재사용하면 함수의 메모리 주소 값을 저장했다가 다시 사용하는 것과 같다.

따라서 React 함수형 컴포넌트 내에서 다른 함수의 인자로 넘기거나 자식 컴포넌트의 props로 넘길 때 예상치 못한 성능 문제를 막을 수 있다.


useCallback 예시

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

const App = () => {
  const [num, setNum] = useState(0);
  const [light, setLight] = useState(true);

  const darkMode = {
    backgroundColor: light ? "White" : "grey",
    color: light ? "grey" : "white"
  };

  // const plus = () => {
  //  return [num + 10, num + 100];
  // };
  
  const plus = useCallback(() => {
    return [num + 10, num + 20];
  }, [num]);

  const handleChange = (event) => {
    if (Number(event.target.value)) {
      setNum(Number(event.target.value));
    }
  };

  return (
    <>
      <div style={darkMode} className="darkMode">
        <input
          type="number"
          className="input"
          value={num}
          onChange={handleChange}
        />

        <button
          className={(light ? "light" : "dark") + " button"}
          onClick={() => setLight((prevLight) => !prevLight)}
        >
          {light ? "dark mode" : "light mode"}
        </button>

        <List plus={plus} />
      </div>
    </>
  );
}

export default App;


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

const List = ({ plus }) => {
  const [items, setItems] = useState([]);

  useEffect(() => {
    console.log('re-rendering');
    setItems(plus());
  }, [plus]);

  return (
    <div>
      {items.map((item) => (
        <div key={item}>{item}</div>
      ))}
    </div>
  );
}

export default List;

위 코드는 input에 number를 입력하면 +10, +20을 더한 값이 출력되고 dark mode 버튼을 누르면 background-color가 변하는 코드이다.

input에 number를 입력하면 상태가 변경되어 컴포넌트가 리렌더링되면서 함수가 다시 생성되고 're-rendering'이 출력된다. 이는 정상적인 출력이지만 dark mode 버튼을 눌렀을때도 동일하게 're-rendering'이 출력된다.

그 이유는 dark mode 버튼을 클릭했을 때도 상태가 변경되어 리렌더링이 발생하고 함수가 다시 생성되면서 메모리의 다른 공간에 저장된다. plus에는 이전과는 다른 메모리 주소가 할당되었기 때문에 List.js의 useEffect는 plus가 변경되었다고 판단하고 종속성이 변경됨에 따라 콜백 함수를 실행하여 're-rendering'이 출력되는 것이다.

plus에 useCallback을 사용하면 dark mode 버튼을 클릭해도 새로운 함수가 생성되면서 참조하는 메모리 주소가 바뀌는 것을 방지하고 메모이제이션 된 함수를 재사용하기 때문에 useEffect는 plus가 바뀌었다고 판단하지 않는다.

plus는 메모이제이션된 함수의 주소가 들어있고 변경되지 않았으므로 렌더링이 발생하지 않고 useEffect의 콜백 함수도 실행되지 않기 때문에 're-rendering'이 출력되지 않는다.


useCallback의 의존성 배열

  const plus = useCallback(() => {
    return [num + 10, num + 20];
  }, [num]);

useCallback의 의존성 배열에 아무 값도 넣지 않게되면(빈 배열) 렌더링 시 한번만 실행된다. 함수를 메모이제이션 해줬을 당시의 상태값이 유지되기 때문에 의존성 배열에 해당하는 상태를 넣어 함수를 업데이트 시켜줘야한다.

profile
주니어 프론트엔드 개발자 uk입니다.

0개의 댓글