useCallback
React를 사용하다 보면 컴포넌트가 생각보다 자주 리렌더링되거나, 자식 컴포넌트가 의미 없이 반복해서 렌더링되는 문제를 경험하게 됩니다. 이럴 때 자주 등장하는 해결책이 바로 useCallback 훅입니다.
useCallback 은 함수의 참조값을 유지해 불필요한 렌더링을 줄이는 데 도움을 주는 도구이지만, 언제 써야 효과가 있고, 언제 쓰지 말아야 하는지에 대한 명확한 기준 없이 무작정 사용하는 경우가 많습니다.
이 글에서는 useCallback 의 기본 개념부터, 실제 사용 예제, 그리고 사용을 권장하는 상황과 피해야 할 상황까지 모두 정리합니다. 성능 최적화를 위해 useCallback 을 고민하고 있다면, 이 글이 그 판단에 확실한 기준을 줄 수 있을 거예요.
1. useCallback
React에서 제공하는 훅으로, 특정 콜백 함수의 메모이제이션(memoization)을 도와줍니다. 쉽게 말해, 컴포넌트가 리렌더링될 때마다 매번 새로운 함수를 생성하지 않고, 의존성 배열(dependency array)에 명시된 값이 바뀌지 않는 한 동일한 함수 객체를 재사용할 수 있도록 해줍니다.
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
2. 주요 개념 요약
항목 | 설명 |
목적 | 불필요한 함수 재생성 방지 |
사용 위치 | 주로 자식 컴포넌트에 props 로 함수를 전달할 때 |
의존성 배열 | 의존성이 변경될 때만 새로운 함수 생성 |
3. 왜 useCallback이 필요한가요?
React는 컴포넌트가 리렌더링될 때, 함수도 새로 정의되기 때문에 참조값이 매번 바뀝니다. 이로 인해 하위 컴포넌트가 React.memo 로 최적화되어 있더라도, props 로 전달되는 함수가 매번 다르게 인식되어 불필요한 리렌더링이 발생할 수 있습니다.
예시 - useCallback 없이 발생하는 문제
const Parent = () => {
const handleClick = () => {
console.log('clicked');
};
return <Child onClick={handleClick} />;
};
const Child = React.memo(({ onClick }) => {
console.log('Child render');
return <button onClick={onClick}>Click me</button>;
});
Parent 가 리렌더링되면 handleClick 함수는 새로운 참조값으로 재생성되므로, Child 는 항상 리렌더링됩니다.
useCallback을 사용한 최적화
const Parent = () => {
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return <Child onClick={handleClick} />;
};
- 이제 Parent 가 리렌더링되더라도 handleClick 함수는 의존성이 바뀌지 않는 한 같은 참조값을 유지하므로, Child 는 불필요하게 리렌더링되지 않습니다.
4. 언제 사용해야 할까?
a. 사용 권장 상황
i. React.memo와 함께 자식 컴포넌트에 함수를 props로 전달하는 경우
const Child = React.memo(({ onClick }) => {
console.log('Child render');
return <button onClick={onClick}>Click</button>;
});
- 부모 컴포넌트가 리렌더링될 때마다 함수가 새로 만들어지면, 자식 컴포넌트도 계속 리렌더링됩니다.
- 이런 경우 useCallback 을 사용해 참조값을 유지해주면, 자식 컴포넌트의 불필요한 렌더링을 방지할 수 있습니다.
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
ii. 함수를 useEffect, useMemo, useReducer 등에 의존성으로 넣을 때
useEffect(() => {
doSomething();
}, [callback]); // callback이 매번 바뀌면 이 useEffect도 계속 실행됨
- React 훅에서는 함수도 의존성 배열에 넣어야 할 때가 있습니다. 하지만 함수가 매 렌더링마다 새로 만들어지면 useEffect 등이 의도치 않게 자주 실행될 수 있습니다.
- 이런 경우 useCallback 을 사용해 함수가 불필요하게 바뀌지 않도록 할 수 있습니다.
b. 사용을 피해야 할 상황
i. 함수가 매우 가볍고, 자주 변경되어야 하는 경우
const handleChange = (e) => setValue(e.target.value);
- 이런 함수는 그냥 다시 만들어도 전혀 부담이 없습니다. 오히려 useCallback으로 감싸면 메모이제이션에 오히려 비용이 들 수 있습니다.
- 즉, 최적화하려는 비용보다 메모이제이션 유지 비용이 더 클 수 있습니다.
ii. 의존성이 자주 바뀌는 함수
const handleClick = useCallback(() => {
console.log(count);
}, [count]);
- count 가 자주 바뀐다면 handleClick 도 매번 새로 만들어지기 때문에 사실상 useCallback 효과가 거의 없습니다.
- 이럴 때는 그냥 일반 함수로 두거나, 정말 성능 이슈가 있다면 로직 자체를 리팩토링해서 무거운 부분만 따로 분리하는 게 좋습니다.
c. 주의사항
- 의존성 배열을 정확히 설정해야 합니다. 누락되면 stale value(오래된 값)를 참조할 수 있습니다.
- 무조건적으로 useCallback 을 남발하면 성능이 개선되기보다는 오히려 나빠질 수도 있습니다. (메모이제이션 비용 자체가 존재하기 때문)
- useCallback(fn, []) 은 실제로는 컴포넌트 최초 마운트 시 단 한 번만 생성되므로, 내부에서 상태를 참조하는 경우 주의가 필요합니다.
5. 마무리
- useCallback 은 함수의 참조값을 유지하여 불필요한 리렌더링을 막아주는 훅입니다.
- 주로 React.memo 와 함께 자식 컴포넌트를 최적화할 때 사용합니다.
- 모든 함수에 일괄적으로 사용하는 것이 아니라, 최적화가 필요한 곳에 선별적으로 사용하는 것이 중요합니다.
함께 보면 좋은 자료
블로그 글 :
[React Hooks] useState부터 커스텀 훅까지
React Hooks React 16.8부터 등장한 Hooks(훅)은 함수형 컴포넌트에서도 상태 관리와 사이드 이펙트를 다룰 수 있게 해주며, 컴포넌트 구조를 더욱 직관적으로 바꿨습니다. 이 글에서는 자주 사용되는
dachaes-devlogs.tistory.com
'프레임워크와 라이브러리 > React' 카테고리의 다른 글
[React Lifecycle] 컴포넌트는 언제 태어나고 사라질까? (0) | 2025.04.22 |
---|---|
[xlsx 라이브러리] 리액트에서 Excel 파일 읽고, 수정하고, 다운로드하기 (0) | 2025.04.21 |
[key] 리액트에서 key로 index를 쓰면 안 되는 이유 (0) | 2025.04.16 |
[클래스 컴포넌트와 함수 컴포넌트] 리액트의 컴포넌트 (0) | 2025.04.15 |
[Advanced React Hooks] useReducer부터 useImperativeHandle까지 (0) | 2025.04.11 |