
React
useCallback
React ‘Use Callback’ 훅은 메모된 콜백 기능을 반환한다.
⭐ 메모를 다시 계산할 필요가 없도록 값을 캐싱하는 것으로 생각한다.
이를 통해 리소스 집약적인 기능을 분리하여 모든 렌더에서 자동으로 실행되지 않도록 할 수 있다.
useCallback 훅은 종속성 중 하나가 업데이트될 때만 실행된다.
성능을 향상시킬 수 있다.
⭐ useCallback과 useMemo 훅은 유사하다. 주요 차이점은 useMemo는 메모된 값을 반환하고 useCallback은 메모된 기능을 반환한다는 것이다.
문제
useCallback을 사용하는 한 가지 이유는 구성 요소가 변경되지 않는 한 구성 요소가 다시 렌더링되는 것을 방지하기 위함이다.
이 예에서는 Todos 구성 요소가 변경되지 않는 한 Todos 구성 요소가 다시 렌더링되지 않는다고 생각할 수 있다.
예제 – index.js
import { useState } from "react"; import ReactDOM from "react-dom/client"; import Todos from "./Todos"; const App = () => { const [count, setCount] = useState(0); const [todos, setTodos] = useState([]); const increment = () => { setCount((c) => c + 1); }; const addTodo = () => { setTodos((t) => [...t, "New Todo"]); }; return ( <> <Todos todos={todos} addTodo={addTodo} /> <hr /> <div> Count: {count} <button onClick={increment}>+</button> </div> </> ); }; const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />);
예제 – Todos.js
import { memo } from "react"; const Todos = ({ todos, addTodo }) => { console.log("child render"); return ( <> <h2>My Todos</h2> {todos.map((todo, index) => { return <p key={index}>{todo}</p>; })} <button onClick={addTodo}>Add Todo</button> </> ); }; export default memo(Todos);
이것을 실행해 보고 count increment 버튼을 클릭한다.
Todos 구성 요소는 Todos가 변경되지 않더라도 다시 렌더링된다는 것을 알 수 있다.
왜 이렇게 안 되나? 우리는 메모를 사용하고 있기 때문에 카운트가 증가하면 Todos 상태나 addTodo 기능이 변경되지 않기 때문에 Todos 구성 요소가 다시 렌더링되어서는 안 된다.
이것은 “참조 평등”이라고 불리는 것 때문이다.
구성 요소가 다시 렌더링될 때마다 그 기능이 다시 생성된다.
이 때문에 실제로 addTodo 기능이 변경되었다.
해결책
이를 해결하기 위해 필요하지 않은 경우 ‘useCallback’ 훅을 사용하여 기능이 재생되지 않도록 할 수 있다.
‘useCallback’ 훅을 사용하여 ‘Todos’ 구성 요소가 불필요하게 재렌더링되는 것을 방지한다.
예제 – index.js
import { useState, useCallback } from "react"; import ReactDOM from "react-dom/client"; import Todos from "./Todos"; const App = () => { const [count, setCount] = useState(0); const [todos, setTodos] = useState([]); const increment = () => { setCount((c) => c + 1); }; const addTodo = useCallback(() => { setTodos((t) => [...t, "New Todo"]); }, [todos]); return ( <> <Todos todos={todos} addTodo={addTodo} /> <hr /> <div> Count: {count} <button onClick={increment}>+</button> </div> </> ); }; const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />);
예제 – Todos.js
import { memo } from "react"; const Todos = ({ todos, addTodo }) => { console.log("child render"); return ( <> <h2>My Todos</h2> {todos.map((todo, index) => { return <p key={index}>{todo}</p>; })} <button onClick={addTodo}>Add Todo</button> </> ); }; export default memo(Todos);
이제 Todos 구성 요소는 Todos prop가 변경될 때만 다시 렌더링된다.