React хук useCallback
Хук React useCallback
повертає мемоізовану функцію зворотного виклику.
Подумайте про запам’ятовування як про кешування значення, щоб його не потрібно було перераховувати.
Це дозволяє нам ізолювати ресурсомісткі функції, щоб вони не запускалися автоматично при кожному рендері.
Хук useCallback
запускається лише під час оновлення однієї з його залежностей.
Це може покращити продуктивність.
Хуки useCallback
і useMemo
схожі. Основна відмінність полягає в тому, що useMemo
повертає мемоізоване значення, а useCallback
повертає мемоізовану функцію. Ви можете дізнатися більше про useMemo в розділі useMemo.
Проблема
Однією з причин використання useCallback
є запобігання повторному рендерингу компонента, якщо його атрибути не змінено.
У цьому прикладі ви можете подумати, що компонент Todos
не відобразиться повторно, якщо todos
змінити:
Це приклад, схожий на приклад у розділі React.memo.
Приклад:
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);
Спробуйте запустити це та натисніть кнопку збільшення кількості.
Ви помітите, що компонент Todos
повторно відображається, навіть якщо todos
не змінюються.
Чому це не працює? Ми використовуємо memo
, тому компонент Todos
не повинен повторно відтворюватися, оскільки ані стан todos
, ані функція addTodo
не змінюються, коли кількість збільшується.
Це через те, що називається "referential equality" ("референційна рівність").
Кожного разу, коли компонент повторно рендериться, його функції створюються заново. Через це функція 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
.