개발일지
useCallback 본문
📌 useCallback란?
useCallback은 리렌더링 사이에 함수 정의를 캐시 할 수 있게 해주는 React훅입니다.
const cachedFn = useCallback(fn, dependencies)
📌 useCallback
최상위 컴포넌트에서 useCallback을 호출하려 리렌더링 사이에 함수 정의를 캐시 합니다:
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는 초기 렌더링을 하는 동안 함수를 반환합니다(호출하지 않습니다). 다음 렌더링에서 React는 마지막 렌더링 이후 dependencies가 변경되지 않았다면 동일한 함수를 다시 제공합니다. 그렇지 않으면 현재 렌더링 중에 전달한 함수를 제공하고 나중에 재사용할 수 있도록 저장합니다. React는 함수를 호출하지 않습니다. 함수는 반환되므로 호출 시기와 여부를 결정할 수 있습니다.
- dependencies: fn 코드 내에서 참조된 모든 반응형 값의 배열입니다. 반응형 값에는 props, state, 컴포넌트 본문 내부에서 직접 선언한 모든 변수 및 함수가 포함됩니다. 린터가 React용으로 구성된 경우, 모든 반응형 값이 의존성으로 올바르게 지정되었는지 확인합니다. React는 Object.is비교 알고리즘을 사용하여 각 의존성을 이전 값과 비교합니다.
📍 반환값
초기 렌더링에서 useCallback은 전달한 fn함수를 반환합니다. 렌더링 중에는 마지막 렌더링에서 이미 저장된 fn 함수를 반환하거나(의존성이 변경되지 않은 경우), 렌더링 중에 전달했던 fn 함수를 반환합니다.
📍 주의 사항
- useCallback은 훅이므로 컴포넌트 최상위 레벨이나 자체 훅에서만 호출할 수 있습니다. 반복문이나 조건문 내부에서는 호출할 수 없습니다. 필요한 경우 새로운 컴포넌트로 추출하고 state를 그 안으로 옮기세요.
- React는 특별한 이유가 없는 한 캐시 된 함수를 버리지 않습니다.
📌 사용 방법
1️⃣ 컴포넌트 리렌더링 건너뛰기
렌더링 성능을 최적화할 때 자식 컴포넌트에 전달하는 함수를 캐시해야 할 때가 있습니다. 먼저 이를 수행하는 방법에 대한 구문을 살펴본 다음 어떤 경우에 유용한지 알아보겠습니다.
컴포넌트의 리렌더링 사이에 함수를 캐시 하려면, 해당 함수의 정의를 useCallback 훅으로 감싸세요:
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은 의존성이 변경되지 전까지는 리렌더링에 대해 함수를 캐시 합니다.
2️⃣ 메모된 콜백에서 state 업데이트하기
때로는 메모된 콜백의 이전 state를 기반으로 state를 업데이트해야 할 수도 있습니다.
이 handlerAddRodo 함수는 다음 할 일을 계산 하기 위해 todos를 의존성으로 지정하였습니다:
function TodoList() {
const [todos, setTodos] = useState([]);
const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos([...todos, newTodo]);
}, [todos]);
// ...
일반적으로 메모화된 함수는 가능한 적은 의존성을 갖기를 원할 것입니다. 다음 state를 계산하기 위해 일부 state만 읽어야 하는 경우, 대신 업데이터 함수를 전달하여 해당 의존성을 제거할 수 있습니다:
function TodoList() {
const [todos, setTodos] = useState([]);
const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos(todos => [...todos, newTodo]);
}, []);// ✅ todos에 대한 의존성이 필요하지 않음
// ...
여기서는 todos를 의존성으로 만들고 내부에서 읽는 대신, state를 업데이트하는 방법에 대한 지시사항(todos => [...todos, newTodo])을 React에 전달합니다.
3️⃣ Effect가 너무 자주 발동되지 않도록 하기
때론 Effect 내부에서 함수를 호출하고 싶은 경우가 있습니다:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
function createOptions() {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}
useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
// ...
이로 인해 문제가 발생합니다. 모든 반응형 값은 Effect의 의존성으로 선언해야 합니다. 그러나 createOptions을 의존성으로 선언하면 Effect가 채팅방에 계속 재연결하게 됩니다:
useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // 🔴 문제: 이 의존성은 렌더링시마다 변경됨
// ...
이 문제를 해결하려면 Effect에서 호출해야 하는 함수를 useCallback으로 감싸면 됩니다:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const createOptions = useCallback(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // ✅ roomId 변경시에만 변경됨
useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // ✅ createOptions 변경시에만 변경됨
// ...
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,
};
}
이렇게 하면 훅의 소비자가 필요할 때 자신의 코드를 최적화할 수 있습니다.
'리액트 공식문서 스터디' 카테고리의 다른 글
useTransition (0) | 2024.06.24 |
---|---|
useMemo, useCallback 발표 자료 (0) | 2024.06.20 |
useMemo (0) | 2024.06.17 |
useEffect, useLayoutEffect 발표 자료 (1) | 2024.06.13 |
useLayoutEffect (0) | 2024.06.11 |