번역 자료 / / 2025. 5. 26. 17:28

[react 번역] useCallback

출처: https://react.dev/reference/react/useCallback


개요

useCallback은 컴포넌트가 리렌더링될 때마다 함수 정의를 캐시(저장)할 수 있게 해주는 React Hook입니다.

const cachedFn = useCallback(fn, dependencies)

Reference

useCallback(fn, dependencies)

컴포넌트의 최상위에서 useCallback을 호출하여 함수 정의를 리렌더링 사이에 캐시할 수 있습니다.

import { useCallback } from 'react';

export default function ProductPage({ productId, referrer, theme }) {
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]);
}

파라미터

  • fn: 캐시하고 싶은 함수. 어떤 인자든 받을 수 있고, 어떤 값이든 반환할 수 있습니다. React는 이 함수를 직접 호출하지 않고, 반환만 해줍니다.
  • dependencies: 함수 내부에서 참조하는 모든 반응형 값(예: props, state, 컴포넌트 내부 변수/함수 등)의 배열. 의존성 배열이 이전 렌더와 동일하다면, 이전에 저장된 함수를 반환합니다.

반환값

  • 최초 렌더에서는 전달한 fn을 반환합니다.
  • 이후 렌더에서는 의존성 배열이 바뀌지 않았다면 이전에 저장된 함수를, 바뀌었다면 새로 전달한 함수를 반환합니다.

주의사항

  • useCallback은 반드시 컴포넌트의 최상위(혹은 커스텀 Hook 내부)에서만 호출해야 하며, 반복문/조건문 내부에서는 사용할 수 없습니다.
  • React는 특별한 이유가 없는 한 캐시된 함수를 버리지 않습니다. (예: 개발 중 파일 수정, 컴포넌트가 suspend될 때 등)
  • 성능 최적화가 목적이 아니라면, state 변수나 ref가 더 적합할 수 있습니다.

사용법

자식 컴포넌트의 불필요한 리렌더링 방지

자식 컴포넌트에 함수를 prop으로 전달할 때, 매번 새로운 함수가 생성되어 불필요한 리렌더링이 발생할 수 있습니다. 이럴 때 useCallback으로 함수를 감싸면, 의존성이 바뀌지 않는 한 동일한 함수가 전달되어 리렌더링을 방지할 수 있습니다.

import { useCallback } from 'react';

function ProductPage({ productId, referrer, theme }) {
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]);

  return (
    <div className={theme}>
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}

자식 컴포넌트가 React.memo로 감싸져 있다면, prop이 바뀌지 않는 한 리렌더링을 건너뜁니다.

메모이즈된 콜백에서 state 업데이트

state를 읽어서 새로운 state를 계산하는 경우, 의존성 배열에 state를 넣지 않고 updater 함수를 사용할 수 있습니다.

function TodoList() {
  const [todos, setTodos] = useState([]);
  const handleAddTodo = useCallback((text) => {
    const newTodo = { id: nextId++, text };
    setTodos(todos => [...todos, newTodo]);
  }, []); // todos를 의존성에 넣지 않아도 됨
}

Effect의 불필요한 실행 방지

Effect 내부에서 함수를 호출해야 할 때, 그 함수가 매번 새로 생성된다면 Effect가 불필요하게 자주 실행될 수 있습니다. 이럴 때 useCallback으로 함수를 감싸거나, 아예 Effect 내부에 함수를 정의하면 의존성 문제를 줄일 수 있습니다.

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');
  const createOptions = useCallback(() => {
    return {
      serverUrl: 'https://localhost:1234',
      roomId: roomId
    };
  }, [roomId]);

  useEffect(() => {
    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]);
}

더 좋은 방법은 Effect 내부에 함수를 직접 정의하는 것입니다.

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');
  useEffect(() => {
    function createOptions() {
      return {
        serverUrl: 'https://localhost:1234',
        roomId: roomId
      };
    }
    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);
}

커스텀 Hook 최적화

커스텀 Hook에서 반환하는 함수도 useCallback으로 감싸면, Hook을 사용하는 쪽에서 성능 최적화를 할 수 있습니다.

function useRouter() {
  const { dispatch } = useContext(RouterStateContext);
  const navigate = useCallback((url) => {
    dispatch({ type: 'navigate', url });
  }, [dispatch]);
  const goBack = useCallback(() => {
    dispatch({ type: 'back' });
  }, [dispatch]);
  return { navigate, goBack };
}

트러블슈팅

매 렌더마다 useCallback이 다른 함수를 반환함

의존성 배열을 두 번째 인자로 반드시 전달해야 합니다. 의존성 배열이 없으면 매번 새로운 함수가 반환됩니다.

function ProductPage({ productId, referrer }) {
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }); // 🔴 의존성 배열이 없으므로 매번 새 함수 반환
}

아래처럼 의존성 배열을 추가해야 합니다.

function ProductPage({ productId, referrer }) {
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]); // ✅ 불필요한 함수 재생성 방지
}

리스트 아이템마다 useCallback을 호출하고 싶을 때

반복문(예: map) 내부에서 useCallback을 호출하는 것은 허용되지 않습니다. 대신, 각 아이템을 별도의 컴포넌트로 분리하고 그 컴포넌트의 최상위에서 useCallback을 호출하세요.

function ReportList({ items }) {
  return (
    <article>
      {items.map(item =>
        <Report key={item.id} item={item} />
      )}
    </article>
  );
}

function Report({ item }) {
  const handleClick = useCallback(() => {
    sendReport(item)
  }, [item]);
  return (
    <figure>
      <Chart onClick={handleClick} />
    </figure>
  );
}

또는, Report 컴포넌트 자체를 memo로 감싸 prop이 바뀌지 않으면 리렌더링을 건너뛰게 할 수도 있습니다.

const Report = memo(function Report({ item }) {
  function handleClick() {
    sendReport(item);
  }
  return (
    <figure>
      <Chart onClick={handleClick} />
    </figure>
  );
});

출처: https://react.dev/reference/react/useCallback

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유