useCallback 이란?
useCallback은 리렌더링 사이에 함수 정의를 캐시할 수 있게 해주는 React 훅이다.
useCallback, 언제 사용해야 할까
간략하게 정리하자면 리렌더링으로 인한 함수의 재정의를 하고 싶지 않을 때 사용한다.
아래의 리스트에 대한 자세한 설명은 사용법 파트에 나와있다.
컴포넌트의 리렌더링 건너뛰기
메모된 콜백에서 state 업데이트하기
Effect가 너무 자주 발동되지 않도록 하기
커스텀 훅 최적화하기
useCallback 의 구성
useMemo는 calculateValue와 dependencies로 나뉘어진다.
fn : 캐시하려는 함수.
어떤 인자도 맏을 수 있고 어떤 값이라도 반환할 수 있다. 만약 dependencies가 변경되지 않았다면 동일한 함수를 제공한다. 변경되었다면 렌더링 중에 fn를 제공하고 재사용할 수 있도록 저장한다.
dependencies : calculateValue 에 참조되는 값들의 목록.
Object.is 비교 알고리즘을 사용하여 각 dependencies를 이전 값과 비교한다.
useMemo(calculateValue, dependencies)
import { useMemo } from 'react';
function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// ...
}
사용시 주의사항
useCallback은 훅이기 때문에 컴포넌트의 최상위 레벨 또는 커스텀 훅에서만 호출이 가능.
사용법
1. 컴포넌트의 렌더링 건너뛰기
function ProductPage({ productId, referrer, theme }) {
// ...
return (
<div className={theme}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);
import { memo } from 'react';
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
// ...
});
위와 같은 상황에서 JavaScript에서 function () {}
또는 () => {}
는 {}
객체 리터럴이 항상 새 객체를 생성하는 것과 유사하게 항상 다른 함수를 생성하기 때문에 ShippingForm의 props는 결코 동일하지 않으며 memo최적화가 작동하지 않는다.
따라서 우리는 아래와 같이 handleSubmit 함수를 변경 시켜줄 필요가 있다.
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
위 처럼 함수를 useCallback
으로 감싸면 리렌더링 사이에 동일한 함수가 되도록 할수 있다.
예시는 리액트 공식문서에 잘 나와있다.
2. 메모된 콜백에서 state 업데이트하기
일반적으로 메모화된 함수는 가능한 적은 의존성을 갖기원하기 때문에 업데이터 함수를 전달하여 의존성을 제거할 수 있다.
function TodoList() {
const [todos, setTodos] = useState([]);
const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos([...todos, newTodo]);
}, [todos]);
// ...
function TodoList() {
const [todos, setTodos] = useState([]);
const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos(todos => [...todos, newTodo]);
}, []); // ✅ No need for the todos dependency
// ✅ todos에 대한 의존성이 필요하지 않음
// ...
3. Effect가 너무 자주 발동되지 않도록 하기
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
function createOptions() {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}
useEffect(() => {
const options = createOptions();
const connection = createConnection();
return () => connection.disconnect();
}, [createOptions]);
위와 같이 useEffect에 의존성을 함수로 선언해주면 렌더링시마다 의존성이 변경되기 때문에 문제가 발생한다.
위와 같은 경우에도 createOptions를 useCallback 으로 감싸주면 해결이 된다.
const createOptions = useCallback(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]);
이렇게 감싸주면 roomId가 변경되었을 때에만 함수가 변경되게 된다.
하지만 가장 좋은 방법은 createOptions 함수 자체를 useEffect 안으로 넣는 방법이다.
그러면 함수가 계속 변경될 일도 없고 useCallback 도 사용할 필요가 없기 때문에 best다!
4. 커스텀 훅 최적화하기
커스텀 훅을 작성하는 경우 반환하는 모든 함수를 useCallback으로 감싸는 것이 좋다.
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,
};
}
이렇게 작성해 놓으면 훅의 소비자가 필요할 때 자신의 코드를 최적화할 수 있다.
그래서 useMemo 와 useCallback의 차이가 뭐야?
둘의 차이점은 캐시 할 수 있는 항목에 있다.
useMemo는 호출한 함수의 결과를 캐시하고 useCallback은 함수자체를 캐시한다.
import { useMemo, useCallback } from 'react';
function ProductPage({ productId, referrer }) {
const product = useData('/product/' + productId);
const requirements = useMemo(() => {
// 함수를 호출하고 그 결과를 캐시합니다.
return computeRequirements(product);
}, [product]);
const handleSubmit = useCallback((orderDetails) => {
// 함수 자체를 캐시합니다.
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
return (
<div className={theme}>
<ShippingForm requirements={requirements} onSubmit={handleSubmit} />
</div>
);
}
useMemo
는 위의 예제에서 product가 변경되지 않는 한 computeRequirements를 호출한 결과를 캐시한다. 이렇게 하면 ShippingForm을 리렌더링할 필요 없이 requirements를 전달할 수 있다.
useCallback은 위의 예제에서 제공한 함수를 호출하지 않는다. 대신 제공한 함수를 캐시하여 handleSubmit 자체가 변경되지 않도록 한다. 이렇게 하면 ShippingForm을 리렌더링할 필요없이 handleSubmit 함수를 전달할 수 있다.