Skip to main content

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를 확실히 진행한 다음 사용하는 것이 가장 바람직해보인다.

Related Links