useCallback 은 주로 자식 컴포넌트에게 함수를 props로 전달할 때 사용합니다. 해당 함수를 메모이제이션 시킴으로써 의존성 배열값이 변경되지 않으면 동일함 함수 참조를 유지하므로 자식 컴포넌트의 불필요한 리렌더링을 방지할 수 있습니다.
[ 문제의 상황 ]

[app.js]
import './App.css';
import {useState} from "react";
import Lists from "./components/Lists";
import Form from "./components/Form";
function App() {
console.log('App.js Rendering')
const [todoData,setTodoData] = useState([])
const [inputValue, setInputValue] = useState('')
const handleSubmit = (e) =>{
e.preventDefault()
let newTodo ={
id: Date.now(),
title: inputValue,
completed: false
}
setTodoData([...todoData,newTodo])
setInputValue('')
}
// useCallback() 적용 대상
const handleClick = (id) => {
let newTodoData = todoData.filter((data) => data.id !== id);
setTodoData(newTodoData);
localStorage.setItem('todoData', JSON.stringify(newTodoData));
};
return (
<div className="flex items-center justify-center w-screen h-screen bg-blue-100 ">
<div className="w-full p-6 m-4 bg-white rounded shadow lg:w-3/4 lg:max-w-lg">
<div className="flex justify-between mb-3">
<h1>할 일 목록</h1>
</div>
<Lists todoData={todoData} setTodoData={setTodoData} handleClick={handleClick}/>
<Form handleSubmit={handleSubmit} inputValue={inputValue} setInputValue={setInputValue} />
</div>
</div>
);
}
export default App;
1. Todo 앱에서 해야할 일을 입력만 하는데도 Lists, List 컴포넌트가 리렌더링 된다.
원인: app.js가 리렌더링 되면서 handleClick 함수가 새로 만들어져 리렌더링이 발생한 것.
자식 컴포넌트에서 React.memo를 사용해 1차 최적화를 시켰지만 렌더링 시 부모 컴포넌트의 함수가 새로 생성되는 것은 어쩔 수 없으므로 리렌더링이 발생했다.
// 변경 전
const myFunction = (param) => {
~~~~
};
->
// 변경 후
const myFunction = useCallback((param) => {
~~~
},[]);
1. 함수 내에서 참조하는 state, props 가 있다면 의존성 배열에 추가해 주면 된다.
2. useCallback() 으로 인해 의존성 배열 데이터들이 변하지 않는다면 함수를 새로 생성하지 않는다.
3. 의존성 배열에 아무것도 없다면, 최초 렌더링 시에만 함수가 생성되고 이후부터는 동일한 참조 값을 사용하는 함수가 된다.
[app.js]
import './App.css';
import {useState} from "react";
import Lists from "./components/Lists";
import Form from "./components/Form";
function App() {
console.log('App.js Rendering')
const [todoData,setTodoData] = useState([])
const [inputValue, setInputValue] = useState('')
const handleSubmit = (e) =>{
e.preventDefault()
let newTodo ={
id: Date.now(),
title: inputValue,
completed: false
}
setTodoData([...todoData,newTodo])
setInputValue('')
}
// useCallback 적용 후
const handleClick = useCallback((id) => {
let newTodoData = todoData.filter((data) => data.id !== id);
setTodoData(newTodoData);
localStorage.setItem('todoData', JSON.stringify(newTodoData));
},[todoData]);
return (
<div className="flex items-center justify-center w-screen h-screen bg-blue-100 ">
<div className="w-full p-6 m-4 bg-white rounded shadow lg:w-3/4 lg:max-w-lg">
<div className="flex justify-between mb-3">
<h1>할 일 목록</h1>
</div>
<Lists todoData={todoData} setTodoData={setTodoData} handleClick={handleClick}/>
<Form handleSubmit={handleSubmit} inputValue={inputValue} setInputValue={setInputValue} />
</div>
</div>
);
}
export default App;

실제 리렌더링이 필요한 app 과 Form 컴포넌트만 리렌더링 되는 것을 확인할 수 있다.