| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- 알고리즘
- 파이썬
- react
- 항해99
- 백준
- Laravel
- c++ 코테
- vue
- 코테
- Python
- 코딩테스트 준비
- 개발자 취업
- 개발자취업
- ML
- 플러터
- Flutter
- til
- 뷰
- 코딩테스트준비
- flutter getx
- DP
- 코테 파이썬
- 오블완
- 안드로이드
- 티스토리챌린지
- C++
- 라라벨
- 파이썬 코테
- 코딩테스트
- 99클럽
- Today
- Total
잡다로그
[React.js] useCallback - useMemo와의 연관성, 4가지 사용법 본문
본 포스팅은 공식 문서를 번역 및 외부 포스팅 참고 후 정리한 글입니다.
📌useCallback
useCallback은 리렌더링 간에 함수 정의를 캐싱해주는 훅이다.
이벤트 핸들러 함수가 자주 재생성되는 경우, 하위 컴포넌트에 props로 전달되는 함수가 자주 재생성되는 경우, 렌더링 최적화가 필요한 경우 등에 사용된다.
useCallback은 성능 최적화를 위한 용도로만 사용해야 한다. 만약 코드가 useCallback없이 작동하지 않는다면, 먼저 근본적인 문제를 해결해야 한다.
const cachedFn = useCallback(fn, dependencies)
매개변수
- fn : 캐싱할 함숫값. 첫 렌더링에서 이 함수를 반환한다. 다음 렌더링에서 dependencies이 변경되었다면 이번 렌더링에서 전달한 함수를 반환하고 나중에 재사용할 수 있도록 이를 저장한다.
- dependencies : fn 내에서 참조되는 모든 반응형 값(props, state, 컴포넌트 안에서 선언된 모든 변수와 함수)의 목록. 항목 수가 일정해야 하며, 인라인으로 작성해야 한다.
⚠️ useCallback은 컴포넌트 최상위 레벨 또는 자체 훅에서만 호출할 수 있고, 반복문이나 조건문 내부에서 호출할 수 없다. 필요하다면 새 컴포넌트로 분리해서 state를 새 컴포넌트로 옮겨야 한다. (* 모든 Hook의 주의사항)
useMemo와의 연관성
리렌더링 방지와 성능 최적화 키워드를 들으면, useMemo가 떠오를 것이다.
useMemo는 useCallback과 함께 자주 쓰인다. 두 hook은 모두 무언가 전달할 때 memoixation(캐싱)할 수 있게 해주어 자식 컴포넌트를 최적화할 때 유용하다. 차이점은 무엇을 캐싱하는지 이다.
- useMemo : 호출한 함수의 결과값 캐싱 → 즉, 연산값 재사용
- useCallback : 함수 자체를 캐싱 → 즉, 함수 재사용
만약 useMemo의 개념이 익숙하다면, 아래와 같이 이해하는 것이 도움이 될 수 있다.
// (React 내부의) 간단한 구현
function useCallback(fn, dependencies) {
return useMemo(() => fn, dependencies);
}
useCallback이 유용한 경우
보통의 사이트는 memoization이 필요하지 않다. 그러나 앱이 도형을 이동하는 것 같이 미세한 상호작용을 하는 그림 편집기 같은 경우, memozation이 유용할 수 있다.
useCallback으로 함수를 캐싱하는 것은 다음과 같은 경우에 유용하다.
- memo로 감싸진 컴포넌트에 prop으로 넘길 때, 넘긴 함수가 나중에 어떤 Hook의 의존성으로 사용될 때.
- 예를 들어, useCallback으로 감싸진 다른 함수가 이 함수에 의존하거나, useEffect에서 이 함수에 의존할 때
다음 원칙을 따르면 굳이 memoization을 사용하지 않고, 가독성을 지키고 특정 상호작용을 가속할 수도 있다.
- 컴포넌트가 다른 컴포넌트를 시각적으로 감싸고 있을 때 JSX을 자식으로 받게 한다.
- 가능한 한 로컬 상태를 선호하고, 컴포넌트 간 상태 공유를 과도하게 하지 않는다. (ex. 폼이나 항목이 hover되었는지)
- 렌더링 로직을 순수하게 유지한다.
- 상태를 업데이트하는 불필요한 Effects를피한다.
- Effects에서 불필요한 의존성을 제거한다.
useCallback 사용법1: 컴포넌트의 리렌더링 건너뛰기
컴포넌트가 리렌더링될 때 모든 자식도 재귀적으로 재렌더링된다. 따라서 리렌더링에 많은 계산이 요구되면 컴포넌트 최적화가 필요해진다.
import { useCallback } from 'react';
function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
함수를 useCallback 훅으로 감싸서 자식 컴포넌트에 넘기는 함수를 캐싱할 수 있다.
useCallback에게 1) 리렌더링 간에 캐싱할 함수 정의, 2) 함수에서 사용되는 컴포넌트 내부의 모든 값을 포함하고 있는 의존성 목록 을 전달해야 한다.
위의 handleSubmit 함수를 ProductPage(부모)에서 ShippingForm(자식) 컴포넌트로 전달한다고 가정할 때,
function ProductPage({ productId, referrer, theme }) {
// ...
return (
<div className={theme}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);
ProductPage가 다른 theme(prop)값으로 리렌더링할 때, ShippingForm 컴포넌트도 같이 리렌더링된다.
이 때 아래 코드와 같이 ShippingForm을 memo로 감싸면, 마지막 렌더링과 동일한 props(여기서, handleSubmit())일 때 리렌더링을 건너뛸 수 있다.
import { memo } from 'react';
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
// ...
});
이 때, useCallback 없이 handleSubmit을 정의한다면, 아래와 같다.
function ProductPage({ productId, referrer, theme }) {
// theme이 바뀔때마다 다른 함수가 될 것...
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}
return (
<div className={theme}>
{/* ... 그래서 ShippingForm의 props는 같은 값이 아니므로 매번 리렌더링 할 것임.*/}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
자바스크립트에서 function () {} 나 () => {}은 항상 다른 함수를 생성한다. 따라서 SippingForm의 props는 절대 같아질 수 없고 memo 최적화는 동작하지 않을 것이다.
function ProductPage({ productId, referrer, theme }) {
// React에게 리렌더링 간에 함수를 캐싱하도록 요청한다...
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]); // ...이 의존성이 변경되지 않는 한...
return (
<div className={theme}>
{/* ...ShippingForm은 같은 props를 받게 되고 리렌더링을 건너뛸 수 있다.*/}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
이렇게 handleSubmit을 useCallback으로 감싸서 리렌더링 같에 같은 함수라는 것을 보장해줄 수 있다.
useCallback 사용법2: Memoized 콜백에서 상태 업데이트하기
memoized 콜백에서 이전 상태를 기반으로 상태를 업데이트해야 할 때가 있다.
function TodoList() {
const [todos, setTodos] = useState([]);
const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos(todos => [...todos, newTodo]);
}, []); // ✅ todos 의존성은 필요하지 않습니다.
// ...
handleAddTodo함수는 todo로부터 다음 할 일을 계산하여 이를 의존성에 명시하려고 했지만, 다음 상태 계산을 위해 어떤 상태를 읽는 경우는 업데이트 함수를 대신 넘겨주는 것이 좋다.
useCallback 사용법3: Effect가 너무 자주 실행되는 것을 방지하기
Effect 안에서 함수를 호출해야 할 때, 모든 반응형 값이 의존성으로 선언되어야 한다. 그러나 어떤 의존성은 매 렌더링마다 변경되곤 한다. 예를 들어, 아래와 같이 createOptions를 의존성으로 선언하면 Effect가 채팅방과 계속 재연결되는 문제가 발생한다.
useEffect(() => {
const options = createOptions();
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // 🔴 문제점: 이 의존성은 매 렌더링마다 변경된다.
// ...
이를 해결하기 위해, Effect에서 호출하려는 함수를 useCallback으로 감쌀 수 있다.
const createOptions = useCallback(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // ✅ roomId가 변경될 때만 변경됩니다.
이는 roomId가 같다면, createOptions 함수는 같다는 것을 보장한다. 그러나, 함수를 Effect 안으로 이동시켜 함수 의존성을 제거하는 것이 더 좋다.
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
function createOptions() { // ✅ useCallback이나 함수 의존성이 필요하지 않다.
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}
const options = createOptions();
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ roomId가 변경될 때만 변경된다.
// ...
useCallback 사용법4: 커스텀 Hook 최적화하기
커스텀 Hook을 작성하는 경우, 반환하는 모든 함수를 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,
};
}
이렇게 하면 Hook을 사용하는 컴포넌트가 필요할 때 가지고 있는 코드를 최적화할 수 있다.
Source:
https://ko.react.dev/reference/react/useCallback
useCallback – React
The library for web and native user interfaces
ko.react.dev
https://react.vlpt.us/basic/18-useCallback.html
18. useCallback 를 사용하여 함수 재사용하기 · GitBook
No results matching ""
react.vlpt.us
'Web > React' 카테고리의 다른 글
| Tanstack Query 시작하기 - 데이터 변경하기, useMutation (응답 처리, 변수 전달 등) (0) | 2024.12.11 |
|---|---|
| 비동기, Promise, async/await (ft. useQuery의 queryFn) (0) | 2024.11.30 |
| Tanstack Query 시작하기 - 데이터 불러오기, 새로고침 (useQuery, queryClient.invalidateQueries 등) (0) | 2024.11.11 |
| [React.js] Props대신 Context 활용하기, useContext (ft. Prop drilling) (0) | 2024.09.27 |
| [React.js] Firebase 연동 후 CRUD 기능 구현 (3) - Update (Delete) (0) | 2024.09.19 |