useCallback
함수에 대해 메모이제이션을 수행해 컴포넌트 렌더링 성능을 최적화 합니다.
원활한 이해를 위해 몇 가지 짚고 넘어가자.
함수형 컴포넌트도 결국엔 함수다.
함수형 컴포넌트가 렌더링 되는 것은 함수를 호출하는 것과 같은 의미다.
함수를 호출하면 내부의 모든 변수가 초기화 된다는 당연한 사실을 되새겨보자.
const exFunc = useCallback((num) => {
return num + 1;
}, [item]);
useCallback은 useMemo와 유사하다.
하지만 useCallback은 인자로 전달한 콜백함수 그 자체를 메모이제이션 한다.
원하는 함수를 useCallback으로 감싸주기만 하면 된다.
자바스크립트 함수를 변수에 할당할 수 있다는 당연한 사실을 되새겨보자.
따라서 특정 함수에 useCallback을 적용하면 컴포넌트가 재렌더링 될 때 해당 변수는 새로운 함수 객체를 할당받지 않을 수 있다.
import { useState, useEffect, useCallback } from 'react';
function App() {
const [number, setNumber] = useState(0);
const [toggle, setToggle] = useState(true);
const exFunc = useCallback(() => {
console.log(`exFunc: number: ${number}`);
return;
}, [number]);
useEffect(() => {
console.log("exFunc 변경됨!");
}, [exFunc]);
return (
<div>
<input
type="number"
value={number}
onChange={e => setNumber(e.target.value)}
/>
<button onClick={() => setToggle(prev => !prev)>
{toggle.toString()}
</button>
<br />
<button onClick={exFunc}>Call exFunc</button>
</div>
);
}
number 상태를 변경하면 다음과 같은 일들이 발생한다.
결과적으로 number 상태가 업데이트 될 때마다 콘솔에 "exFunc 변경됨!" 로그가 찍힌다.
하지만 토글 버튼을 눌러 toggle 상태를 업데이트하면 어떨까?
이때는 number 상태가 업데이트 되지 않기 때문에 useEffect는 실행되지 않는다.
변수 exFunc가 메모이제이션 된 함수 객체만을 바라보기 때문이다.
이번에는 보다 더 현실적인 예제를 살펴보자.
App 컴포넌트에서 Box 컴포넌트로 createBoxStyle 함수를 prop의 형태로 넘기고 있다.
// ✅ App.js
import { useState } from "react";
import "./App.css";
import Box from "./components/Box";
function App() {
const [size, setSize] = useState(100);
const [isDark, setIsDark] = useState(false);
const createBoxStyle = () => ({
backgroundColor: "pink",
width: `${size}px`,
height: `${size}px`,
});
return (
<div style={{ background: isDark ? "black" : "white" }}>
<input
type="number"
value={size}
onChange={e => setSize(e.target.value)}
/>
<button onClick={() => setIsDark(prev => !prev)}>Change Theme</button>
<Box createBoxStyle={createBoxStyle} />
</div>
);
}
export default App;
// ✅ Box.js
import { useEffect, useState } from "react";
function Box({ createBoxStyle }) {
const [style, setStyle] = useState({});
useEffect(() => {
console.log("박스야 놀자 📦");
setStyle(createBoxStyle());
}, [createBoxStyle]);
return <div style={style} />;
}
export default Box;
버튼을 클릭해서 isDark 상태를 업데이트 하면 다음과 같은 일들이 발생한다.
isDark 상태가 업데이트 됐는데(=다크모드 여부가 바뀌었는데) Box 컴포넌트 내 useEffect가 실행되는 것은 이상하다.
독립 관계이어야 하는 서로 관련 없는 요소들이 의존하고 있는 상황이다.
이 문제는 어떻게 해결할 수 있을까?
const createBoxStyle = useCallback(
() => ({
backgroundColor: "pink",
width: `${size}px`,
height: `${size}px`,
}),
[size]
);
App 컴포넌트 내 createBoxStyle 함수를 useCallback으로 감싸면 된다.
이때 size 상태가 업데이트 될 때만 변수 createBoxStyle에 새로운 함수 객체가 할당될 수 있또록 의존성 배열에 size 상태를 주입한다.
이렇게 함수에 useCallback을 적용하면 Box 컴포넌트 내 useEffect는 더이상 실행되지 않는다.