useCallback
useCallback
- хук React, який дозволяє кешувати визначення функції між повторними рендерингами.
const cachedFn = useCallback(fn, dependencies)
Довідник
useCallback(fn, dependencies)
Викличте useCallback
на верхньому рівні вашого компонента, щоб кешувати визначення функції між повторними рендерами:
import { useCallback } from 'react';
export default function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
Дивіться більше прикладів нижче.
Параметри
fn
: Значення функції, яке ви хочете кешувати. Вона може приймати будь-які аргументи і повертати будь-які значення. React поверне (не викличе!) вашу функцію під час першого рендерингу. При наступних рендерингах React поверне вам ту саму функцію, якщозалежності
не змінилися з моменту останнього рендерингу. В іншому випадку, він надасть вам функцію, яку ви передали під час поточного рендерингу, і збереже її на випадок, якщо вона може бути використана пізніше. React не буде викликати вашу функцію. Функція повертається вам, щоб ви могли вирішити, коли і чи викликати її.залежності
: список усіх реактивних значень, на які посилаються всередині кодуfn
. Реактивні значення включають пропси, стан і всі змінні та функції, оголошені безпосередньо у тілі вашого компонента. Якщо ваш лінтер налаштований на React, він буде перевіряти, що кожне реактивне значення правильно вказане як залежність. Список залежностей повинен мати постійну кількість елементів і бути записаний в лінію як[dep1, dep2, dep3]
. React порівняє кожну залежність з попереднім значенням, використовуючи алгоритм порівнянняObject.is
.
Повернення
При початковому рендерингу useCallback
повертає передану вами функцію fn
.
Під час наступних рендерингів він або повертатиме вже збережену функцію fn
з останнього рендерингу (якщо залежності не змінилися), або повертатиме функцію fn
, яку ви передали під час цього рендерингу.
Застереження
useCallback
є хуком, тому ви можете викликати його тільки на верхньому рівні вашого компонента або ваших власних хуків. Ви не можете викликати його всередині циклів або умов. Якщо вам це потрібно, витягніть новий компонент і перемістіть стан до нього.- React не викидатиме кешовану функцію, якщо для цього немає конкретної причини. Наприклад, під час розробки React викидає кеш, коли ви редагуєте файл вашого компонента. Як в розробці, так і у виробництві, React викидає кеш, якщо ваш компонент призупиняється під час початкового монтування. У майбутньому React може додати більше можливостей, які використовують переваги відкидання кешу - наприклад, якщо в майбутньому React додасть вбудовану підтримку віртуалізованих списків, то має сенс відкидати кеш для елементів, які прокручуються за межами області перегляду віртуалізованої таблиці. Це буде добре, якщо ви покладаєтесь на
useCallback
виключно як на оптимізацію продуктивності. В іншому випадку, змінна стану або посилання можуть бути більш доречними.
Використання
Пропуск повторного рендерингу компонентів
Під час оптимізації продуктивності рендерингу іноді потрібно кешувати функції, які ви передаєте дочірнім компонентам. Давайте спочатку розглянемо синтаксис, як це зробити, а потім подивимося, в яких випадках це може бути корисно.
Щоб кешувати функцію між рендерингами вашого компонента, обгорніть її визначення у useCallback
Хук:
import { useCallback } from 'react';
function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
// ...
Вам потрібно передати дві речі до useCallback
:
- Визначення функції, яку потрібно кешувати між повторними відображеннями.
- Список залежностей
CodeStep> включаючи кожне значення з вашого компонента, що використовується у вашій функції.</li></ol> <p>При початковому рендерингу функція <CodeStep data-step="3">повернула </CodeStep> ви отримаєте з <code>useCallback буде функцією, яку ви передали. На наступних рендерингах React порівнює залежності
та ), useCallback
поверне ту саму функцію, що й раніше. ІнакшеuseCallback
поверне функцію, яку ви передали у this render.Іншими словами,
useCallback
кешує функцію між повторними рендерингами, поки не зміняться її залежності.Давайте розглянемо приклад, щоб побачити, коли це може бути корисним.
Скажімо, ви передаєте функцію
handleSubmit
зProductPage
до компонентаShippingForm
:function ProductPage({ productId, referrer, theme }) { // ... return ( <div className={theme}> <ShippingForm onSubmit={handleSubmit} /> </div> );
Ви помітили, що перемикання пропсу
theme
на мить призупиняє роботу програми, але якщо ви видалите<ShippingForm />
з вашого JSX, то програма працює швидко. Це говорить про те, що варто спробувати оптимізувати компонентShippingForm
.За замовчуванням, коли компонент перерендериться, React рекурсивно перерендерить усі його дочірні елементи. Ось чому, коли
ProductPage
перерендериться з іншоютемою
,ShippingForm
компонент також перерендериться. Це добре для компонентів, які не потребують багато обчислень для повторного рендерингу. Але якщо ви переконалися, що повторний рендер повільний, ви можете вказатиShippingForm
пропустити повторний рендер, якщо його пропси такі самі, як і під час останнього рендеру, обернувши його уmemo
:import { memo } from 'react'; const ShippingForm = memo(function ShippingForm({ onSubmit }) { // ... });
З цією зміною
ShippingForm
пропустить повторний рендеринг, якщо всі його пропси будуть такими самими , як і під час останнього рендерингу. Ось де кешування функції стає важливим! Припустимо, що ви обчислилиhandleSubmit
безuseCallback
:function ProductPage({ productId, referrer, theme }) { // Every time the theme changes, this will be a different function... function handleSubmit(orderDetails) { post('/product/' + productId + '/buy', { referrer, orderDetails, }); } return ( <div className={theme}> {/* ... so ShippingForm's props will never be the same, and it will re-render every time */} <ShippingForm onSubmit={handleSubmit} /> </div> ); }
У JavaScript
function () {}
або() => {}
завжди створює іншу функцію, подібно до того, як літерал об'єкта{}
завжди створює новий об'єкт. Зазвичай це не було б проблемою, але це означає, що пропсиShippingForm
ніколи не будуть однаковими, і вашаmemo
оптимізація не працюватиме. Ось деuseCallback
стає у нагоді:function ProductPage({ productId, referrer, theme }) { // Tell React to cache your function between re-renders... const handleSubmit = useCallback((orderDetails) => { post('/product/' + productId + '/buy', { referrer, orderDetails, }); }, [productId, referrer]); // ...so as long as these dependencies don't change... return ( <div className={theme}> {/* ...ShippingForm will receive the same props and can skip re-rendering */} <ShippingForm onSubmit={handleSubmit} /> </div> ); }
Загорнувши
handleSubmit
уuseCallback
, ви гарантуєте, що це буде та сама функція між рендерами (доки не зміняться залежності). Вам не потрібно обгортати функцію у , якщо тільки ви не робите цього з якоїсь конкретної причини. У цьому прикладі причиною є те, що ви передаєте її компоненту, обгорнутому уmemo
,, і це дозволяє йому пропустити повторний рендеринг. Є й інші причини, за яких вам може знадобитисяuseCallback
, які описано далі на цій сторінці.Вам слід покладатися лише на
useCallback
як на оптимізацію продуктивності. Якщо ваш код не працює без нього, спочатку знайдіть основну проблему і виправте її. Після цього ви можете додатиuseCallback
назад.Як useCallback пов'язаний з useMemo?
Ви часто бачите
useMemo
поряд зuseCallback
. Вони обидва корисні, коли ви намагаєтеся оптимізувати дочірній компонент. Вони дозволяють вам запам'ятовувати (або, іншими словами, кешувати) щось, що ви передаєте далі:import { useMemo, useCallback } from 'react'; function ProductPage({ productId, referrer }) { const product = useData('/product/' + productId); const requirements = useMemo(() => { // Calls your function and caches its result return computeRequirements(product); }, [product]); const handleSubmit = useCallback((orderDetails) => { // Caches your function itself post('/product/' + productId + '/buy', { referrer, orderDetails, }); }, [productId, referrer]); return ( <div className={theme}> <ShippingForm requirements={requirements} onSubmit={handleSubmit} /> </div> ); }
Різниця в тому, щовони дозволяють вам кешувати:
useMemo
кешує результат виклику вашої функції. У цьому прикладі він кешує результат викликуcomputeRequirements(product)
, щоб він не змінювався, якщо не змінивсяпродукт
. Це дозволяє передавати об'єктrequirements
вниз без зайвого повторного рендерингуShippingForm
. За необхідності React викличе функцію, яку ви передали під час рендерингу, щоб обчислити результат.useCallback
кешує саму функцію. На відміну відuseMemo
, він не викликає надану вами функцію. Натомість він кешує надану вами функцію так, щоhandleSubmit
сам не змінюється, доки не змінитьсяproductId
абоreferrer
. Це дозволяє вам передавати функціюhandleSubmit
вниз без зайвого повторного рендерингуShippingForm
. Ваш код не буде виконано, доки користувач не надішле форму.
Якщо ви вже знайомі з
useMemo
, можливо, вам буде корисно думати проuseCallback
так:// Simplified implementation (inside React) function useCallback(fn, dependencies) { return useMemo(() => fn, dependencies); }
Чи потрібно скрізь додавати useCallback?
Якщо ваша програма подібна до цього сайту, і більшість взаємодій є грубими (наприклад, заміна сторінки або цілого розділу), запам'ятовування зазвичай не потрібне. З іншого боку, якщо ваша програма більше схожа на редактор малюнків, і більшість взаємодій є деталізованими (наприклад, переміщення фігур), то запам'ятовування може виявитися дуже корисним.
Кешування функції за допомогою
useCallback
має сенс лише у кількох випадках:- Ви передаєте його як проп до компонента, обгорнутого у
memo
. Ви хочете пропустити повторний рендер, якщо значення не змінилося. Запам'ятовування дозволяє перерендерити компонент лише у разі зміни залежностей. - Функція, яку ви передаєте, пізніше використовується як залежність деякого хука. Наприклад, від неї залежить інша функція, обгорнута в
useCallback
, або ви залежите від цієї функції зuseEffect.
В інших випадках немає ніякої користі від обгортання функції в
useCallback
. Істотної шкоди від цього також немає, тому деякі команди вирішують не думати про окремі випадки, а запам'ятовувати якомога більше. Недоліком цього є те, що код стає менш читабельним. Крім того, не всяке запам'ятовування є ефективним: одного значення, яке є "завжди новим", достатньо, щоб порушити запам'ятовування для цілого компонента.Зверніть увагу, що
useCallback
не заважає створювати функцію. Ви завжди створюєте функцію (і це добре!), але React ігнорує це і повертає вам кешовану функцію, якщо нічого не змінилося.На практиці, ви можете зробити багато запам'ятовування непотрібним, дотримуючись кількох принципів:
- Коли компонент візуально обгортає інші компоненти, дозвольте йому приймати JSX як дочірні. Тоді, якщо компонент-обгортка оновлює власний стан, React знатиме, що його дочірні компоненти не потребують повторного рендерингуь.
- Надавайте перевагу локальному стану і не піднімайте стан вище , ніж це необхідно. Не зберігайте перехідні стани, як-от форми та елементи, наведені на вершину дерева або у глобальній бібліотеці станів.
- Зберігайте логіку рендерингу чистою. Якщо повторний рендеринг компонента викликає проблему або створює помітний візуальний артефакт, це вада у вашому компоненті! Виправте ваду замість того, щоб додавати запам'ятовування.
- Уникайте непотрібних ефектів, які оновлюють стан. Більшість проблем з продуктивністю у React-додатках спричинені ланцюжками оновлень від ефектів, які змушують ваші компоненти рендерити знову і знову.
- Спробуйте видалити непотрібні залежності з ефектів.Наприклад, замість запам'ятовування часто простіше перемістити якийсь об'єкт або функцію всередину ефекту або за межі компонента.
Якщо певна взаємодія все ще гальмує, використайте профайлер React Developer Tools, щоб побачити, які компоненти отримують найбільшу користь від запам'ятовування, і додайте запам'ятовування там, де це необхідно. Ці принципи полегшують налагодження та розуміння ваших компонентів, тому варто дотримуватися їх у будь-якому випадку. У довгостроковій перспективі ми досліджуємо можливість автоматичного використання гранульованої пам'яті, щоб вирішити цю проблему раз і назавжди.
Пропуск повторного рендерингу з useCallback
та memo
У цьому прикладі ShippingForm
компонент штучно сповільнено, щоб ви могли побачити, що відбувається, коли React-компонент, який ви рендерите, дійсно повільний. Спробуйте збільшити лічильник і перемкнути тему.
Інкрементування лічильника відчувається повільним, оскільки воно змушує сповільнений ShippingForm
рендерити повторно. Це очікувано, оскільки лічильник змінився, і вам потрібно відобразити новий вибір користувача на екрані.
Далі спробуйте переключити тему. Завдяки useCallback
таʼ memo
це працює швидко, незважаючи на штучне сповільнення! ShippingForm
пропустив повторний рендеринг, оскільки функція handleSubmit
не змінилася. Функція handleSubmit
не змінилася, оскільки productId
та referrer
(ваші залежності useCallback
) не змінилися з часу останнього рендеру.
import { useState } from 'react';
import ProductPage from './ProductPage.js';
export default function App() {
const [isDark, setIsDark] = useState(false);
return (
<>
<label>
<input
type="checkbox"
checked={isDark}
onChange={e => setIsDark(e.target.checked)}
/>
Dark mode
</label>
<hr />
<ProductPage
referrerId="wizard_of_oz"
productId={123}
theme={isDark ? 'dark' : 'light'}
/>
</>
);
}
import { useCallback } from 'react';
import ShippingForm from './ShippingForm.js';
export default function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
return (
<div className={theme}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
function post(url, data) {
// Imagine this sends a request...
console.log('POST /' + url);
console.log(data);
}
import { memo, useState } from 'react';
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
const [count, setCount] = useState(1);
console.log('[ARTIFICIALLY SLOW] Rendering <ShippingForm />');
let startTime = performance.now();
while (performance.now() - startTime < 500) {
// Do nothing for 500 ms to emulate extremely slow code
}
function handleSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const orderDetails = {
...Object.fromEntries(formData),
count
};
onSubmit(orderDetails);
}
return (
<form onSubmit={handleSubmit}>
<p><b>Note: <code>ShippingForm
is artificially slowed down!
);
});
export default ShippingForm;
label {
display: block; margin-top: 10px;
}
input {
margin-left: 5px;
}
button[type="button"] {
margin: 5px;
}
.dark {
background-color: black;
color: white;
}
.light {
background-color: white;
color: black;
}
Завжди повторний рендеринг компонента
У цьому прикладі реалізація ShippingForm
також штучно сповільнена, щоб ви могли побачити, що відбувається, коли якийсь React-компонент, який ви рендерите, дійсно працює повільно. Спробуйте збільшити лічильник і перемкнути тему.
На відміну від попереднього прикладу, перемикання теми тепер також відбувається повільно! Це пов'язано з тим, що у цій версії немає виклику useCallback
, тому handleSubmit
завжди є новою функцією, і сповільнений ShippingForm
компонент не може пропустити повторний рендеринг.
import { useState } from 'react';
import ProductPage from './ProductPage.js';
export default function App() {
const [isDark, setIsDark] = useState(false);
return (
<>
<label>
<input
type="checkbox"
checked={isDark}
onChange={e => setIsDark(e.target.checked)}
/>
Dark mode
</label>
<hr />
<ProductPage
referrerId="wizard_of_oz"
productId={123}
theme={isDark ? 'dark' : 'light'}
/>
</>
);
}
import ShippingForm from './ShippingForm.js';
export default function ProductPage({ productId, referrer, theme }) {
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}
return (
<div className={theme}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
function post(url, data) {
// Imagine this sends a request...
console.log('POST /' + url);
console.log(data);
}
import { memo, useState } from 'react';
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
const [count, setCount] = useState(1);
console.log('[ARTIFICIALLY SLOW] Rendering <ShippingForm />');
let startTime = performance.now();
while (performance.now() - startTime < 500) {
// Do nothing for 500 ms to emulate extremely slow code
}
function handleSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const orderDetails = {
...Object.fromEntries(formData),
count
};
onSubmit(orderDetails);
}
return (
<form onSubmit={handleSubmit}>
<p><b>Note: <code>ShippingForm
is artificially slowed down!
);
});
export default ShippingForm;
label {
display: block; margin-top: 10px;
}
input {
margin-left: 5px;
}
button[type="button"] {
margin: 5px;
}
.dark {
background-color: black;
color: white;
}
.light {
background-color: white;
color: black;
}
Втім, ось той самий код з вилученим штучним сповільненням. Відсутність useCallback
відчутна чи ні?
import { useState } from 'react';
import ProductPage from './ProductPage.js';
export default function App() {
const [isDark, setIsDark] = useState(false);
return (
<>
<label>
<input
type="checkbox"
checked={isDark}
onChange={e => setIsDark(e.target.checked)}
/>
Dark mode
</label>
<hr />
<ProductPage
referrerId="wizard_of_oz"
productId={123}
theme={isDark ? 'dark' : 'light'}
/>
</>
);
}
import ShippingForm from './ShippingForm.js';
export default function ProductPage({ productId, referrer, theme }) {
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}
return (
<div className={theme}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
function post(url, data) {
// Imagine this sends a request...
console.log('POST /' + url);
console.log(data);
}
import { memo, useState } from 'react';
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
const [count, setCount] = useState(1);
console.log('Rendering <ShippingForm />');
function handleSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const orderDetails = {
...Object.fromEntries(formData),
count
};
onSubmit(orderDetails);
}
return (
<form onSubmit={handleSubmit}>
<label>
Number of items:
<button type="button" onClick={() => setCount(count - 1)}>–</button>
{count}
<button type="button" onClick={() => setCount(count + 1)}>+</button>
</label>
<label>
Street:
<input name="street" />
</label>
<label>
City:
<input name="city" />
</label>
<label>
Postal code:
<input name="zipCode" />
</label>
<button type="submit">Submit</button>
</form>
);
});
export default ShippingForm;
label {
display: block; margin-top: 10px;
}
input {
margin-left: 5px;
}
button[type="button"] {
margin: 5px;
}
.dark {
background-color: black;
color: white;
}
.light {
background-color: white;
color: black;
}
Доволі часто код без запам'ятовування працює добре. Якщо ваші взаємодії є достатньо швидкими, вам може не знадобитися запам'ятовування.
Майте на увазі, що вам потрібно запустити React у виробничому режимі, вимкнути React Developer Tools та використовувати пристрої, подібні до тих, що мають користувачі вашого застосунку, щоб отримати реалістичне уявлення про те, що насправді сповільнює ваш застосунок.
Оновлення стану із запам'ятованого зворотного виклику
Іноді може знадобитися оновити стан на основі попереднього стану із запам'ятованого зворотного виклику.
Ця функція handleAddTodo
визначає todos
як залежну, оскільки обчислює наступний todos з неї:
function TodoList() {
const [todos, setTodos] = useState([]);
const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos([...todos, newTodo]);
}, [todos]);
// ...
Зазвичай ви хочете, щоб запам'ятовані функції мали якомога менше залежностей. Якщо ви читаєте деякий стан лише для обчислення наступного стану, ви можете вилучити цю залежність, передавши функцію updater замість:
function TodoList() {
const [todos, setTodos] = useState([]);
const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos(todos => [...todos, newTodo]);
}, []); // ✅ No need for the todos dependency
// ...
Тут замість того, щоб зробити todos
залежністю і прочитати її всередині, ви передаєте інструкцію про те, як як оновити стан (todos => [...todos, newTodo]
) до React. Детальніше про функції оновлювача.
Запобігання ефекту від надто частого застосування
Іноді вам може знадобитися викликати функцію зсередини Ефекту:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
function createOptions() {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}
useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
// ...
Це створює проблему. Кожне реактивне значення має бути оголошено як залежність вашого ефекту. Однак, якщо ви оголосите createOptions
як залежність, це призведе до того, що ваш ефект буде постійно перепідключатися до кімнати чату:
useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // 🔴 Problem: This dependency changes on every render
// ...
Щоб вирішити цю проблему, ви можете обгорнути функцію, яку потрібно викликати з ефекту, в useCallback
:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const createOptions = useCallback(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // ✅ Only changes when roomId changes
useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // ✅ Only changes when createOptions changes
// ...
Це гарантує, що функція createOptions
буде однаковою у різних рендерингах, якщо roomId
однакова. Втім, ще краще усунути потребу у функціональній залежності. Перемістіть вашу функцію всередину ефекту:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
function createOptions() { // ✅ No need for useCallback or function dependencies!
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Only changes when roomId changes
// ...
Тепер ваш код простіший і не потребує useCallback
. Дізнайтеся більше про вилучення залежностей ефектів.
Оптимізація користувацького хука
Якщо ви пишете користувацький хук, рекомендується обгорнути всі функції, які він повертає, у useCallback
:
function useRouter() {
const { dispatch } = useContext(RouterStateContext);
const navigate = useCallback((url) => {
dispatch({ type: 'navigate', url });
}, [dispatch]);
const goBack = useCallback(() => {
dispatch({ type: 'back' });
}, [dispatch]);
return {
navigate,
goBack,
};
}
Це гарантує, що споживачі вашого хука зможуть оптимізувати власний код за потреби.
Налагодження
Щоразу, коли мій компонент рендерить, useCallback
повертає іншу функцію
Переконайтеся, що ви вказали масив залежностей як другий аргумент!
Якщо ви забудете масив залежностей, useCallback
щоразу повертатиме нову функцію:
function ProductPage({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}); // 🔴 Returns a new function every time: no dependency array
// ...
Виправлена версія, що передає масив залежностей як другий аргумент:
function ProductPage({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]); // ✅ Does not return a new function unnecessarily
// ...
Якщо це не допомогло, то проблема у тому, що принаймні одна з ваших залежностей відрізняється від попереднього рендерингу. Ви можете налагодити цю проблему за допомогою ручного виведення залежностей у консоль:
const handleSubmit = useCallback((orderDetails) => {
// ..
}, [productId, referrer]);
console.log([productId, referrer]);
Після цього ви можете клацнути правою кнопкою миші на масивах з різних рендерингів у консолі і вибрати "Зберегти як глобальну змінну" для обох. Якщо припустити, що перший масив було збережено як temp1
, а другий - як temp2
, ви можете скористатися консоллю браузера, щоб перевірити, чи кожна залежність в обох масивах однакова:
Object.is(temp1[0], temp2[0]); // Is the first dependency the same between the arrays?
Object.is(temp1[1], temp2[1]); // Is the second dependency the same between the arrays?
Object.is(temp1[2], temp2[2]); // ... and so on for every dependency ...
Коли ви знайдете, яка саме залежність порушує запам'ятовування, або знайдіть спосіб її видалити, або запам'ятайте її також.
Мені потрібно викликати useCallback
для кожного елемента списку у циклі, але це не дозволено
Припустимо, що компонент Chart
обгорнуто у memo
. Ви хочете пропустити повторний рендеринг кожного Chart
у списку при повторному рендерингу компонента ReportList
. Однак, ви не можете викликати useCallback
у циклі:
function ReportList({ items }) {
return (
<article>
{items.map(item => {
// 🔴 You can't call useCallback in a loop like this:
const handleClick = useCallback(() => {
sendReport(item)
}, [item]);
return (
<figure key={item.id}>
<Chart onClick={handleClick} />
</figure>
);
})}
</article>
);
}
Натомість витягніть компонент для окремого елемента і помістіть useCallback
туди:
function ReportList({ items }) {
return (
<article>
{items.map(item =>
<Report key={item.id} item={item} />
)}
</article>
);
}
function Report({ item }) {
// ✅ Call useCallback at the top level:
const handleClick = useCallback(() => {
sendReport(item)
}, [item]);
return (
<figure>
<Chart onClick={handleClick} />
</figure>
);
}
Як варіант, ви можете видалити useCallback
в останньому фрагменті і натомість обгорнути сам Report
у memo
. Якщо проп item
не буде змінено, Report
пропустить повторний рендеринг, тому Chart
також пропустить повторний рендеринг:
function ReportList({ items }) {
// ...
}
const Report = memo(function Report({ item }) {
function handleClick() {
sendReport(item);
}
return (
<figure>
<Chart onClick={handleClick} />
</figure>
);
});