[SeSACx코딩온] useCallback
useCallback을 이용한 성능 최적화
1. useCallback이란?
useCallback
은 함수 메모이제이션(Memoization)을 통해 컴포넌트가 리랜더링될 때마다 동일한 함수를 반복적으로 생성하는 문제를 해결합니다. 특정 의존성 배열에 기반하여, 의존성이 변경되지 않으면 기존 함수를 재사용합니다. 이를 통해 불필요한 함수 생성과 자원의 낭비를 줄여 성능을 최적화할 수 있습니다. 특히, 자식 컴포넌트에 콜백 함수를 전달하거나, 복잡한 연산을 포함한 함수를 사용할 때 유용합니다.
2. 설명
1) UseCallbackEx
컴포넌트
import React, { useState, useCallback } from 'react';
// useCallback
// - 매번 함수를 생성하지 않고, 함수를 기억해두었다가 필요할 때마다 함수를 재사용.
export default function UseCallbackEx() {
const [text, setText] = useState('');
// [before]
// useCallback 안썼을 때
// text 상태가 업데이트 되면 = 컴포넌트 리랜더링 = 코드를 다시 읽는다는 뜻
// = onChangeText 함수도 다시 생성 => 불필요한 작업
const onChangeText = (e) => {
setText(e.target.value);
}
// [after]
// useCallback 훅으로 함수를 기억하여
// 컴포넌트가 리랜더링 되어도, 의존성 배열에 있는 값이 바뀌지 않는 한, 기존 함수를 재사용
const onChangeTextUseCallback = useCallback((e) => {
setText(e.target.value);
}, []);
return (
<div>
<h1>useCallback ex</h1>
<input type="text" onChange={onChangeTextUseCallback} />
<div>작성한 값: {text || '없음'}</div>
</div>
);
}
- useCallback 미사용
onChangeText
함수는text
상태가 변경될 때마다 새롭게 생성됩니다. 이는 컴포넌트가 리랜더링될 때마다 동일한 코드가 반복적으로 실행되어 함수가 다시 만들어지게 되며, 그 결과 불필요한 자원 낭비가 발생합니다. 예를 들어, 이 함수가 자식 컴포넌트에 전달된다면, 자식 컴포넌트도 불필요하게 리랜더링될 수 있습니다.
- useCallback 사용
useCallback
을 사용하면onChangeTextUseCallback
함수가 메모이제이션됩니다. 이로 인해 컴포넌트가 리랜더링되더라도 의존성 배열에 포함된 값(text
)이 변경되지 않는 한, 이전에 생성된 함수가 재사용됩니다. 이는 불필요한 함수 생성과 자식 컴포넌트의 불필요한 리랜더링을 방지하며, 성능을 개선하는 데 도움을 줍니다.
2) UseCallbackEx2
컴포넌트
import React, { useState, useEffect, useCallback } from 'react';
import axios from 'axios';
export default function UseCallbackEx2({ postId }) {
const [post, setPost] = useState({});
// [before]
const getPost = async () => {
console.log('data fetching...');
// 데이터 요청
const res = await axios.get(
`https://jsonplaceholder.typicode.com/posts//${postId}`
);
setPost(res.data);
};
// useEffect 의존성 배열에 "함수"를 넣으면
// 컴포넌트가 리랜더링 -> 함수가 재생성 (주소값 변경) -> getPost 재호출 => 갯수만큼 계속 랜더링 (무한랜더링)
// useEffect(() => {
// getPost();
// }, [getPost]) // 무한 랜더링으로 인해 주석 처리
// [after]
// useCallback 훅으로 메모이제이션 -> 의존성 배열에 있는 postId가 변경되지 않는 한 컴포넌트는 리랜더링 되지 않음
const getPostUseCallback = useCallback(async () => {
console.log('data fetching...');
const res = await axios.get(
`https://jsonplaceholder.typicode.com/posts//${postId}`
);
setPost(res.data);
}, [postId]);
useEffect(() => {
getPostUseCallback();
}, [getPostUseCallback]);
return (
<div>
<h1>UseCallbackEx2</h1>
{post.id ? post.title : '로딩 중...'}
<br />
</div>
);
}
- useCallback 미사용
getPost
함수는 컴포넌트가 리랜더링될 때마다 새롭게 생성됩니다. 만약 이 함수가useEffect
의 의존성 배열에 포함되어 있다면, 리랜더링 시마다 함수가 다시 생성되고, 그로 인해useEffect
가 무한히 실행될 가능성이 있습니다. 이는 심각한 성능 문제를 일으킬 수 있으며, 실제로는 원하는 동작이 아닐 가능성이 큽니다.
- useCallback 사용
useCallback
을 사용하여getPostUseCallback
함수를 메모이제이션하면,postId
가 변경되지 않는 한 함수는 재사용됩니다.useEffect
의 의존성 배열에 이 메모이제이션된 함수를 포함시키면,postId
가 변경될 때에만 데이터 요청이 수행되며, 불필요한 데이터 요청과 무한 리랜더링 문제를 방지할 수 있습니다. 이는 특히 외부 API 호출이 빈번한 상황에서 성능 최적화에 매우 유용합니다.
3. useMemo
와 useCallback
의 차이점
React에서 useMemo
와 useCallback
은 둘 다 메모이제이션(Memoization)을 통해 성능을 최적화하는 데 사용되지만, 목적과 사용 방법에는 차이가 있습니다.
1) useMemo
-
useMemo
는 컴포넌트가 리랜더링될 때 특정 계산의 결과를 저장해 두고, 동일한 입력값이 주어질 경우 해당 결과를 재사용합니다. 주로 연산 비용이 큰 작업이나 복잡한 계산을 반복적으로 수행하는 경우에 사용됩니다. -
예시
- 대량의 배열 데이터를 필터링하여 새로운 배열을 생성하는 작업의 경우 반복될 때마다 시간이 많이 소요될 수 있습니다.
useMemo
를 사용하면, 입력값이 변경되지 않는 한 이전에 계산된 결과를 재사용하여 불필요한 연산을 줄일 수 있습니다.
const filteredData = useMemo(() => { return Array.filter(item => item.isActive); }, [Array]);
Array
가 변경되지 않는 한filteredData
는 새로 계산되지 않고 이전 값을 그대로 재사용합니다. 이를 통해 컴포넌트 리랜더링 시 불필요한 연산을 방지할 수 있습니다.
- 대량의 배열 데이터를 필터링하여 새로운 배열을 생성하는 작업의 경우 반복될 때마다 시간이 많이 소요될 수 있습니다.
2) useCallback
-
useCallback
은 특정 함수의 재생성을 방지하기 위해 사용됩니다. 컴포넌트가 리랜더링될 때마다 함수가 새로 생성되는 것을 방지하여, 불필요한 리랜더링이나 자원 낭비를 줄입니다. 특히, 자식 컴포넌트에 함수가 props로 전달될 때 유용합니다. -
예시
- 만약 부모 컴포넌트가 자식 컴포넌트에게 어떤 콜백 함수를 전달해야 한다면,
useCallback
을 사용하여 함수가 매번 새로 생성되는 것을 막을 수 있습니다. 이는 자식 컴포넌트가 불필요하게 리랜더링되는 것을 방지할 수 있습니다.
const click = useCallback(() => { console.log('Button clicked'); }, []);
click
함수는 의존성 배열이 빈 상태이므로, 컴포넌트가 리랜더링되더라도 동일한 함수가 재사용됩니다. 이는 자식 컴포넌트가 이 함수를 props로 받아 사용할 때 특히 유용합니다.
- 만약 부모 컴포넌트가 자식 컴포넌트에게 어떤 콜백 함수를 전달해야 한다면,
3) 주요 차이점
(1) 목적
useMemo
는 값을 메모이제이션하여 연산 비용을 줄이기 위한 것이고,useCallback
은 함수 자체를 메모이제이션하여 불필요한 함수 재생성을 막기 위한 것입니다.
(2) 사용 예시
useMemo
는 복잡한 계산이나 대량의 데이터 처리 등에서 사용되며,useCallback
은 콜백 함수가 자주 리랜더링되어 불필요한 자원 낭비를 초래할 때 사용됩니다.
(3) 반환 값
useMemo
는 계산된값
을 반환하며,useCallback
은 메모이제이션된함수
를 반환합니다.
4. useCallback
사용 시 주의 사항
-
useCallback
은 의존성이 자주 변경되거나, 함수의 생성 비용이 낮다면useCallback
을 사용하는 것이 오히려 성능을 저하시킬 수 있습니다. 따라서, 성능 이점을 얻을 수 있는 상황에서만 사용하는 것이 좋습니다. -
자식 컴포넌트에 콜백 함수를 전달할 때, 해당 자식 컴포넌트의 리랜더링을 방지하기 위해 사용되는 것이 일반적입니다. 그렇지 않은 경우에는 굳이 사용하지 않아도 됩니다.