소개
리액트에서는 성능 최적화를 위해 다양한 훅을 제공하고 있습니다.
그 중에서도 useCallback은 함수형 컴포넌트의 성능을 향상시키는 데에 중요한 역할을 합니다.
이 글에서는 useCallback의 개념, 사용법, 그리고 최적화 전략에 대해 간단히 알아보겠습니다.
1. useCallback이란?
useCallback은 함수를 메모이제이션(Memoization)하여, 동일한 함수가 계속해서 재생성되는 것을 방지합니다.
주로 자식 컴포넌트에게 콜백 함수를 전달할 때 사용되며,
이를 통해 불필요한 렌더링을 방지하여 성능을 최적화할 수 있습니다.
useCallback은 값이 아닌 메모이제이션된 콜백 함수를 반환한다.(useMemo와의 차이)
기본적인 사용법은 다음과 같습니다.
const memoizedCallback = useCallback(() => {
// 함수 로직
}, [/* 의존성 배열 */]);
useCallback의 두 번째 인자로 전달되는 의존성 배열은
해당 함수가 의존하는 값들을 명시적으로 지정하는 역할을 합니다.
의존성 배열이 빈 배열인 경우 항상 동일한 함수를 반환합니다.
2. useCallback의 사용법
2.1. 기본 사용
import React, { useState, useCallback } from 'react';
const MyComponent = () => {
const [count, setCount] = useState(0);
// 의존성 배열이 빈 경우, 항상 동일한 함수를 반환
const handleClick = useCallback(() => {
setCount(count + 1);
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
};
위 코드에서 handleClick 함수는 count의 값에 의존하지만,
빈 의존성 배열을 전달하여 항상 동일한 함수를 반환하도록 설정하고 있습니다.
여기에는 큰 함정이 있습니다
위 코드가 의도한 바는 Increment 버튼을 눌렀을 때 화면의 count가 1씩 계속해서 증가해야합니다.
언뜻보면 문제가 없어보입니다만
파란색 문장에 함정이 있습니다.
빈 배열을 전달했기 때문에 항상 동일한 함수를 반환합니다.
handleClick 함수가 생성되는 시기는 MyComponent 컴포넌트가 마운트 되는 시기입니다.
MyComponent 컴포넌트가 마운트 될 때 count의 state는 0입니다.
중요한 점은 동일한 함수 내부에서 호출된 setState의 경우 동일한 State 값을 참조합니다.
의존성 배열이 비어있기 때문에 handleClick 함수는 변하지 않습니다.
즉, handleClick 함수는 재생성되지 않는다는 말입니다.(컴포넌트 마운트 시점에 생성된 함수와 항상 동일)
handleClick 함수가 재생성되지 않으면 최신의 count state를 참조할 수 없습니다.
따라서 handleClick함수에서 참조하는 count의 state는 항상 0입니다. (마운트 시점)
따라서 아무리 눌러봤자 0에 1을 계속해서 더하는 꼴입니다.
화면의 값은 1을 유지할 것입니다.
2.2. 의존성 배열 활용
import React, { useState, useCallback } from 'react';
const MyComponent = () => {
const [count, setCount] = useState(0);
// count 값이 변경될 때만 함수를 재생성
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
};
useCallback의 두 번째 인자로 count를 전달하여,
count 값이 변경될 때 함수를 재생성하도록 설정하고 있습니다.
이렇게 하면 Increment 버튼을 눌렀을 때
함수가 재생성되어 최신의 count state를 참조할 수 있게 됩니다.
원하는 대로 화면에 1씩 올라가게 할수 있습니다.
3. useCallback의 성능 최적화 활용
3.1. 자식 컴포넌트에 콜백 함수 전달
import React, { useState, useCallback } from 'react';
const ChildComponent = ({ onClick }) => {
return <button onClick={onClick}>Click me</button>;
};
const ParentComponent = () => {
const [count, setCount] = useState(0);
// 콜백 함수를 메모이제이션하여 자식 컴포넌트에 전달
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<ChildComponent onClick={handleClick} />
</div>
);
};
useCallback을 사용하여 handleClick 함수를 메모이제이션하여 자식 컴포넌트에 전달하면,
자식 컴포넌트가 불필요하게 다시 랜더링되지 않습니다.
사실 위의 코드의 경우 Click me 버튼을 누를 때마다 count 값이 변화하므로
Click me 버튼을 누를 때마다 handleClick 함수가 재생성됩니다.
그거마저 줄일 수가 있습니다.
바로 함수형 업데이트를 이용하는 것인데요. 간단합니다.
함수형 업데이트란 setState(상태변화함수)에 값이 아닌 함수를 전달하는 것입니다.
setState에 콜백함수로 전달된 함수는 어떤 상황이든 항상 매개변수로 최신의 State를 제공받습니다.
import React, { useState, useCallback } from 'react';
const ChildComponent = ({ onClick }) => {
return <button onClick={onClick}>Click me</button>;
};
const ParentComponent = () => {
const [count, setCount] = useState(0);
// 콜백 함수를 메모이제이션하여 자식 컴포넌트에 전달
const handleClick = useCallback(() => {
setCount((count)=>count + 1);
}, []);
return (
<div>
<p>Count: {count}</p>
<ChildComponent onClick={handleClick} />
</div>
);
};
위 코드를 보면 의아할 수도 있습니다.
두 번째 인자로 빈 배열을 전달했기 때문에
위에서(2.1 기본사용) 말했듯이 컴포넌트 마운트 시점에 생성된 함수가 변하지 않는건 똑같습니다.
count가 변한다해도 함수는 변하지 않습니다.(재생성되지 않습니다.)
여기서의 차이점은 바로 앞서 설명한 함수형 업데이트인데요,
setCount(상태변화함수)를 보시면 값이 아닌 콜백 함수가 전달되었습니다.
강조를 위해 한번더 말하자면
setState에 콜백함수로 전달된 함수는 어떤 상황이든 항상 매개변수로 최신의 State를 제공받습니다.
따라서 함수의 재생성 없이
함수형 업데이트를 통해 최신의 count state를 참조할 수 있게되어
의도한대로 버튼을 누를 때마다 1씩 증가하게 할 수 있습니다.
3.2. 성능 측정과 최적화
useCallback을 사용한 성능 최적화는 실제로 성능 측정을 통해 확인하는 것이 중요합니다.
특히 컴포넌트 규모가 크거나 렌더링 주기가 빈번한 경우에 민감하게 반응하므로,
성능 측정을 통해 실제로 성능 향상이 있는지 확인하는 것이 좋습니다.
4. 주의사항
4.1. 무분별한 사용 주의
useCallback을 무분별하게 사용하면
오히려 메모이제이션의 오버헤드로 인해 성능이 저하될 수 있습니다.
실제 성능 이슈가 있는 상황에서만 사용하는 것이 좋습니다.
4.2. 최적화 필요성 판단
useCallback을 사용한 최적화가 항상 필요한 것은 아닙니다.
컴포넌트의 규모와 특성에 따라 필요한 최적화를 판단해야 합니다.
마무리
useCallback은 리액트에서 함수형 컴포넌트의 성능 최적화에 필수적인 훅 중 하나입니다.
특히 자식 컴포넌트에 콜백 함수를 전달하는 경우에 활용하면 성능을 크게 향상시킬 수 있습니다.
그러나 무분별한 사용은 피하고, 실제 성능 이슈가 있는 상황에서 적절히 활용하는 것이 좋습니다.