useCallback은 리렌더링 사이에 함수 정의를 캐시할 수 있게 해주는 React 훅입니다.
즉, 함수가 정의되고, 리랜더링이 일어나고 의존성 배열이 바뀌지 않는다면 함수는 새로 만들어지지 않고 기존의 함수가 반환됩니다.
사용법
import { useCallback } from 'react';
function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
// ...
- 리렌더링 사이에 캐시할 함수, 함수내에서 사용되는 컴포넌트 내부의 모든 값을 포함하는 의존성 배열
- 렌더링 과정: 초기 렌더링 시 처음에 전달했던 함수 반환 -> 리렌더링발생시, 이전 렌더링에서 전달된 의존성과 비교 -> 의존성이 변경되지 않았다면 이전과 같은 함수 반환 -> 의존성이 변경되었다면 이번 렌더링에서 전달할 함수를 반환
이점
- 리랜더링이 되고, 의존성이 바뀌지 않았다면 캐시된 기존의 함수를 반환함으로써 성능을 최적화할 수 있습니다.
- 함수를 새로 만들지 않고, 기존의 함수를 사용하면 좋은 점은 JavaScript는 내부의 똑같은 함수여도 항상 다른 함수를 생성합니다. 그래서, useCallback을 사용하면 동일한 함수가 되도록 할 수 있습니다.(의존성이 변경되지 않았다면)
useCallback은 어떤 곳에서 사용하면 좋을까?
- 그림 편집기 처럼 인터랙션이 많이 일어나는 곳이 좋습니다. (포토샵, 파워포인트, 일러스트, 미리캔버스 등)
- memo로 감싼 컴포넌트에 prop으로 전달하는 경우, 메모화를 사용하면 의존성이 변경된 경우에만 컴포넌트를 리렌더링할 수 있다.
- 전달한 함수는 나중에 일부 훅의 의존성으로 사용됩니다. (예: useCallback으로 감싼 다른 함수가 이 함수의 의존하거나, useEffect에서 이 함수에 의존하는 경우)
단점
- 코드 가독성이 떨어진다.
- 만약, 항상 새로운 단일 값인 경우 전체 컴포넌트에 대한 메모화가 깨질 수 있다.
- 캐시를 하는 것이기 때문에, 메모리를 사용하게 된다.
메모화할 필요가 없게 만드는 경우는?
1. 컴포넌트가 childern을 이용하여 다른 컴포넌트를 감쌀 경우
- 감싼 컴포넌트 내부에서 state를 업데이트 하더라도, 자식 컴포넌트는 리렌더링이 안 일어난다.
2. 컴포넌트 내부의 state를 선호하고, prop으로 전달해서 사용하는 state는 필요 이상으로 사용하지 않는 경우(state 끌어올리기)
3. 렌더링 로직을 순수하게 유지하는 경우
4. state를 업데이트하는 불필요한 Effect를 제거하는 경우
- React에서 대부분의 성능 문제는 컴포넌트를 반복적으로 렌더링하게 하는 Effect의 업데이트 체인으로 발생한다.
5. Effect에서 불필요한 의존성을 제거한 경우
즉, 메모화하기 전에 위에 처럼 컴포넌트 구조를 만드는 게 좋습니다!
useCallback을 이용한 커스텀 훅 최적화
- 커스텀 훅을 작성하는 경우 반환하는 모든 함수를 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,
};
}
만약, useCallback을 사용했는데 문제가 발생했다면?
1. 두 번째 인자로 의존성 배열을 지정했는지 확인
- 의존성 배열이 없는 경우 매번 새로운 함수를 반환한다.
2. 루프 안에서 uesCallback을 사용할 때, 개별 항목에 대한 컴포넌트를 추출하고 그 컴포넌트에 useCallback을 사용해야된다.
function ReportList({ items }) {
return (
<article>
{items.map(item =>
<Report key={item.id} item={item} />
)}
</article>
);
}
function Report({ item }) {
// ✅ Call useCallback at the top level:
// ✅ useCallback은 컴포넌트의 최상위 레벨에서 호출하세요:
const handleClick = useCallback(() => {
sendReport(item)
}, [item]);
return (
<figure>
<Chart onClick={handleClick} />
</figure>
);
}
'개발 공부' 카테고리의 다른 글
[React] useEffect()를 제거해보자! (1) | 2023.11.26 |
---|---|
개발 버전 관리 (1) | 2023.10.17 |
[React+TypeScript] ShortPolling, LongPolling, SSE, WebSocket 차이점 (0) | 2023.09.24 |
[리액트 공식 문서] Reducer와 Context로 확장하기 (0) | 2023.09.06 |
[리액트 공식 문서] context로 데이터 깊숙이 전달하기 (0) | 2023.09.06 |