Search

useCallback

useCallback()은 이전 편의 useMemo()와 함께 리액트 앱의 최적화를 위해 사용하는 리액트 Hook입니다. 특정 값이나 컴포넌트를 메모이제이션 하는 useMemo()와 달리, useCallback()함수를 메모이제이션(memoization)할 때 사용합니다.
리액트 컴포넌트는 리랜더링을 하게 되면 내부의 모든 것들을 다시 생성합니다. 이 때 컴포넌트 내부에 정의된 함수들도 함께 다시 생성됩니다. useCallback()을 사용하면 컴포넌트가 리랜더링하더라도 함수를 다시 생성하지 않고, 기존 함수를 메모이제이션해두고 그대로 불러와서 사용하도록 할 수 있습니다.
useMemo()와 마찬가지로 useCallback() 또한 무분별한 사용은 오히려 앱 성능을 저하시킵니다. 메모이제이션하는 것도 컴퓨팅 비용이 드는 작업이므로 적절한 경우에만 useCallback()을 사용하는 것이 앱 성능 향상에 도움이 됩니다.

useCallback 사용

const memoizedFunction = useCallback(/* 메모이제이션할 함수 */, [/* 의존성 배열 */]);
JavaScript
복사
기본적인 사용법은 useMemo()의 사용법과 같습니다. 차이점이라면 useMemo()는 값을 반환하지만, useCallback()은 함수(function)를 반환합니다.
useMemo()에 대해 잘 모르신다면 이전 편인 useMemo 편을 확인하세요.
첫번째 인자로 메모이제이션할 함수를 포함하면 됩니다. 두번째 인자는 컴포넌트가 랜더링을 수행할 때 함수를 다시 생성할 조건을 설정하는 의존성 배열(dependency array)입니다. 의존성 배열에 포함된 값에 따라 메모이제이션한 함수를 다시 생성할지에 대한 여부가 결정됩니다.
예를 들면 아래 코드처럼 ab 값을 사용하는 (param) => param + a + b 함수를 메모이제이션할 때, 의존성 배열에 ab를 설정해두면 컴포넌트가 리랜더링할 때 a 또는 b 값이 변경되었을 때만 함수가 다시 생성됩니다.
const memoizedFunction = useCallback( (param) => param + a + b, // 메모이제이션할 함수 [a, b] // 의존성 배열 ); memoizedFunction(10); // 함수를 반환하므로 함수 사용하는 것과 동일하게 사용
JavaScript
복사
useCallback의 의존성 배열 또한 useEffect, useMemo와 그 기능이 같습니다. 의존성 배열에 대해 잘 모르신다면 편을 확인하세요.

컴포넌트의 함수 동등 비교

자바스크립트에서 함수는 일급 객체입니다. 함수는 객체처럼 취급되기 때문에 생성될 때마다 별도의 메모리 주소를 부여받습니다. 따라서 같은 함수를 여러번 생성하게 되면 함수의 내용이 동일하더라도 비교문을 적용했을 때 서로 다른 함수로 판별하게 됩니다.
// (x) => x + 10; 함수를 생성해서 반환하는 팩토리 함수 const functionFactory = () => { return (x) => x + 10; } const functionA = functionFactory(); // functionA = (x) => x + 10; const functionB = functionFactory(); // functionB = (x) => x + 10; console.log(functionA === functionB); // false
JavaScript
복사
functionA와 functionB는 같은 팩토리 함수로 생성해서 같은 함수 내용을 가지고 있지만 비교했을 때 같지 않은 걸로 판별됩니다.
함수의 이런 특징은 리액트에서 컴포넌트가 리랜더링을 하게 될 경우 문제가 될 수 있습니다. 컴포넌트가 리랜더링하게 되면 내부의 함수도 다시 생성되므로 이전 함수와 비교했을때 내용은 같지만 동등 비교했을 때 같지 않은 결과(false)로 나오게 되기 때문입니다.

컴포넌트의 리액트 훅 의존성 배열로 함수를 사용할 경우

위에서 설명한 자바스크립트 함수의 동등 비교 특징은 일반적인 리액트 코드에서 문제가 되지는 않지만 useMemo() 등에 사용되는 의존성 배열에 함수를 포함했을 때는 문제가 됩니다. 리액트의 의존성 배열은 동등비교를 통해 값이 변경되었는지 확인하기 때문입니다. 의존성 배열에 포함된 함수가 리랜더링을 통해 다시 생성될 경우 함수는 이전과 달라진 내용이 없더라도 동등 비교가 다른 것으로 나오기 때문에 useMemo()일 경우 메모이제이션을 소용 없게 만들고 useEffect()일 경우 의도하지 않은 사이드 이펙트를 발생 시키게 됩니다.
아래는 이 현상을 이해하기 예제 코드입니다. <App />은 count 상태를 가지고 있고 +버튼을 누르면 count 상태가 증가하기 때문에 리랜더링이 발생합니다. <App />의 요소 중에는 items라는 큰 사이즈의 배열을 map() 함수로 순환 생성하는 리스트 요소가 있습니다. 리스트가 크기 때문에 불필요한 재생성을 방지하기 위해 useMemo()를 적용해 메모이제이션하고 의존성 배열에는 onClick() 함수만 추가해 놓았습니다. 하지만 실행해서 확인해보면 <App />이 리랜더링 할때마다 리스트는 다시 생성됩니다.
import ReactDOM from 'react-dom/client'; import { useState, useMemo } from 'react'; const App = () => { const [count, setCount] = useState(0); // 리스트로 생성할 10000개의 아이템 배열 😱 const items = Array.from({ length: 10000 }).fill('item'); // 리스트 요소를 클릭할 때 호출하는 함수 const onClick = () => { console.log('아이템 클릭'); }; return ( <> <div> <h1>카운트: {count}</h1> <button onClick={() => setCount((c) => c + 1)}> + </button> </div> <div> {/* 생성되는 li 요소가 많기 때문에 useMemo로 메모이제이션 */} {/* 의존성 배열에는 리스트에서 사용하는 onClick 함수만 추가 */} {useMemo(() => { console.log('🤹🏻‍♂️ 리스트 생성'); return <ul> {items.map((item, index) => <li key={index} onClick={onClick} > {item} </li>)} </ul> }, [onClick])} </div> </> ); }; const rootNode = document.getElementById('root'); ReactDOM.createRoot(rootNode).render(<App />);
JavaScript
복사
10000개의 리스트 재생성을 방지하기 위해 메모이제이션 했지만 의존성 배열로 사용된 onClick 함수가 리랜더링할 때마다 재생성 됨에 따라 리스트도 함께 재생성되고 있습니다.
리스트의 재생성을 막기 위해 onClick() 함수를 useCallback()을 사용해서 메모이제이션하겠습니다. 의존성 배열은 빈 배열로 설정해 마운트(mount)때만 함수가 생성되게 합니다. 다시 결과를 확인해보면 <App />이 리랜더링하더라도 onClick() 함수가 재생성되지 않기 때문에 더 이상 리스트가 재생성되지 않습니다.
import ReactDOM from 'react-dom/client'; import { useCallback, useState, useMemo } from 'react'; const App = () => { const [count, setCount] = useState(0); // 리스트로 생성할 10000개의 아이템 배열 😱 const items = Array.from({ length: 10000 }).fill('item'); // 리스트에서 사용할 함수를 useCallback으로 메모이제이션 const onClick = useCallback(() => { console.log('아이템 클릭'); }, []); return ( <> <div> <h1>카운트: {count}</h1> <button onClick={() => setCount((c) => c + 1)}> + </button> </div> <div> {/* 생성되는 li 요소가 많기 때문에 useMemo로 메모이제이션 */} {/* 의존성 배열에는 리스트에서 사용하는 onClick 함수만 추가 */} {useMemo(() => { console.log('🤹🏻‍♂️ 리스트 생성'); return <ul> {items.map((item, index) => <li key={index} onClick={onClick} > {item} </li>)} </ul> }, [onClick])} </div> </> ); }; const rootNode = document.getElementById('root'); ReactDOM.createRoot(rootNode).render(<App />);
JavaScript
복사
onClick 함수가 useCallback으로 메모이제이션 되어있기 때문에 10000개의 리스트 메모이제이션도 불필요한 재생성을 하지 않습니다.

 useMemo

 useTransition

React Hooks 목록