useCallback
useCallback은 자주 사용하는 함수를 캐시(메모리에 저장)해서, re-render 될 때마다 재사용 하는 React Hook이다.
그렇다면 모든 함수를 useCallback으로 감싸는게 맞지않을까?
다음의 경우를 보자.
useCallback 없이 사용하는 경우
const MyButton = () => {
const [isToggled, setIsToggled] = useState<boolean>(false);
const handleToggle = () => {
setIsToggled(!isToggled);
};
return (
<button type="button" onClick={handleToggle}>
Button
</button>
);
};
위의 코드는 useCallback
을 사용하지 않은 예시이다. 최초 렌더링 시, handleToggle
함수가 메모리에 저장된다. 그리고 <button>
을 클릭하면 이 메모리에 저장된 함수를 실행하는 것이다.
하지만 <button>
을 클릭할 때마다 isToggled
상태값이 업데이트 되고, 이는 곧 re-render를 유발하며 Button
이라고 하는 컴포넌트가 다시 렌더링된다.
그렇게 되면 다시 한번 Button
컴포넌트에 존재하는 코드가 실행이 되며 handleToggle
함수가 새롭게 메모리에 저장된다. 이전에 생성되었던 handleToggle
은 가비지 컬렉터에 의해 메모리에서 제거된다. 또한 isToggled
함수의 메모리 주소는 새롭게 변경된다.
useCallback을 사용하는 경우
이제 useCallback
으로 감싼 경우를 확인해보자.
const MyButton = () => {
const [isToggled, setIsToggled] = useState<boolean>(false);
const handleToggle = useCallback(() => {
setIsToggled(!isToggled);
}, []);
return (
<button type="button" onClick={handleToggle}>
Button
</button>
);
};
이 경우, MyButton 컴포넌트가 최초 렌더링 됐을 때, handleToggle
함수가 메모리에 저장된다. 그리고 이 함수는 별다른 조건 없이([]
) 최초 렌더링 시 생성되고, 이후 동일한 상태가 유지된다.
<button>
을 클릭해서 isToggled
값이 업데이트 되었다고 한다면, handleToggle
함수는 다시 생성되지 않고 다음에도 동일한 메모리 주소로부터 참조된다.
두 가지 방식의 차이점은 뭘까?
우선 useCallback
을 사용하지 않는다면, 매번 새로운 함수를 생성하고, 이전에 생성되었던 함수는 가비지 컬렉터에 의해 제거된다. 따라서 메모리 사용량을 미미하게나마 상대적으로 적게 유지할 수 있다.
useCallback
을 사용하는 경우, Button
이라고 하는 컴포넌트 scope에 존재하기 때문에, Button
의 생명주기를 따라가게 된다. 따라서 Button
컴포넌트가 존재하는 동안, handleToggle
함수는 메모리에 계속 저장 돼 있다.
그래서 핵심은
일단 두 방식이 각각 유용한 경우가 따로 있다. 만약 Child Component에 전달하는 경우나, 함수를 빈번하게 호출할 가능성이 큰 경우, useCallback
을 사용하는 것이 효율적일 수 있다.
하지만 자주 사용되지 않는 함수이거나, 함수가 너무 큰 경우 useCallback
으로 메모리에 계속 저장하는게 비효율적일 수 있다. 또한 코드가 복잡해지거나, 지독한 최적화가 오히려 화를 부를 수도 있다. (ex. dependency array를 제대로 사용하지 못하는 경우 등)
하지만 어디까지나 그 메모리 차이는 매우 미미하다. 결국 함수를 Child Component에 전달하는 경우 등 웬만하면 useCallback
을 사용하되, Performance Measure를 확실히 진행한 다음 사용하는 것이 가장 바람직해보인다.