useCallback은 리렌더링 간에 함수 정의를 캐싱해 주는 React Hook이다.
const cachedFn = useCallback(fn, dependencies)
레퍼런스
useCallback(fn,dependencies)
리렌더링 간에 함수 정의를 캐싱하려면 컴포넌트의 최상단에서 호출을 해야한다.
import { useCallback } from 'react';
export default function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
매개변수
- fn : 캐싱할 함숫값. 이 함수는 어떤 인자나 반환값도 가질 수 있다. React는 첫 렌더링에서 이 함수를 반환한다. 그 이후 렌더링에서 dependencies값이 이전과 같다면 같은 함수를 반환한다.
- dependencies : fn 내에서 참조되는 모든 반응형 값의 목록. 반응형 값은 props와 state, 그리고 컴포넌트 안에서 직접 선언된 모든 변수와 함수를 포함한다.
반환값
최초 렌더링에서는 useCallback은 전달한 fn 함수를 그대로 반환한다.
후속 렌더링에서는 이전 렌더링에서 이미 저장해 두었던 fn 함수를 반환하거나, 현재 렌더링 중에 전달한 fn 함수를 그대로 반환한다.
주의사항
- useCallback은 Hook이므로, 컴포넌트 최상위에서 호출할 수 있다.
- React는 특별한 이유가 없는 한 캐시 된 함수를 삭제하지 않는다.
사용법
컴포넌트의 리렌더링 건너뛰기
렌더링 성능을 최적화할 때 자식 컴포넌트에 넘기는 함수를 캐싱할 필요가 있다.
컴포넌트의 리렌더링 간에 함수를 캐싱하려면 함수 정의를 useCallback Hook으로 감싸줘야 한다.
import { useCallback } from 'react';
function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
// ...
useCallback에게 두 가지 전달해줘야 한다.
1. 리렌더링 간에 캐싱할 함수 정의
2. 함수에서 사용되는 컴포넌트 내부의 모든 값을 포함하고 있는 의존성 목록
처음 렌더링할 때 useCallback으로부터 반환되는 함수는 호출시에 전달할 함수다.
이어지는 렌더링에서 React는 의존성을 이전 렌더링과 비교한다. 의존성 중 하나라도 변한 값이 없다면 이전값을 반환해주고 변했다면 useCallback은 이번 렌더링에서 전달한 함수를 반환한다.
즉, useCallback은 의존성이 변하기 전까지 리렌더링 간에 함수를 캐싱한다.
중요한점은 useCallback은 성능 최적화를 위한 용도로만 사용해야 한다.
useCallback과 useMemo의 차이점?
둘이 함께 사용되는것을 자주 봤을 것이다. 두 hook은 모두 자식 컴포넌트를 최적화할 때 유용하다.
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 : 호출한 함수의 결과값을 캐싱.
- useCallback : 함수 자체를 캐싱.
항상 useCallback을 사용해야 하나?
공식문서 사이트처럼 대부분의 당호작용이 굵직한 경우, 보통 메모이제이션이 필요하지 않다. 반면 앱이 미세한 상호작용을 한느 그림 편집기 같은 경우, 메모이제이션이 매우 유용할 수 있다.
useCallback을 전부 다 사용해도 큰 불이익을 가져오는것도 아니라 일부 팀에서는 따로 생각하지 않고, 가능한 한 많이 사용하기도 한다.
단점으로는 코드의 가독성이 떨어지게 되는 것이다.
그리고 몇가지 원칙을 따르면 많은 메모이제이션을 불필요하게 할 수 있다고 한다.
- 컴포넌트가 다른 컴포넌트를 시각적으로 감싸고 있다면 JSX를 자식으로 받게 하세요. 감싸는 컴포넌트가 자신의 상태를 업데이트하면, React는 자식들은 리렌더링할 필요가 없다는 것을 알게 됩니다.
- 가능한 한 로컬 상태를 선호하고, 컴포넌트 간 상태 공유를 필요 이상으로 하지 마세요. 폼이나 항목이 호버되었는지와 같은 일시적인 상태를 트리의 상단이나 전역 상태 라이브러리에 유지하지 마세요.
- 렌더링 로직을 순수하게 유지하세요. 컴포넌트를 리렌더링하는 것이 문제를 일으키거나 눈에 띄는 시각적인 형체를 생성한다면, 그것은 컴포넌트의 버그입니다! memoization을 추가하는 대신 버그를 해결하세요.
- 상태를 업데이트하는 불필요한 Effects를 피하세요. React 앱에서 대부분의 성능 문제는 Effects로부터 발생한 연속된 업데이트가 컴포넌트를 계속해서 렌더링하는 것이 원인입니다.
- Effects에서 불필요한 의존성을 제거하세요. 예를 들어, memoization 대신 객체나 함수를 Effect 안이나 컴포넌트 외부로 이동시키는 것이 더 간단한 경우가 많습니다.
'리액트 공식문서 읽어보기' 카테고리의 다른 글
| useDeferredValue (0) | 2024.03.06 |
|---|---|
| useDebugValue (0) | 2024.03.03 |
| <Suspense> (1) | 2024.02.25 |
| lazy (0) | 2024.02.25 |
| useState (0) | 2024.02.24 |