useMemo
useMemo — это React-хук, который позволяет кэшировать результат вычисления между перерендерами.
const cachedValue = useMemo(calculateValue, dependencies)
Справочник
useMemo(calculateValue, dependencies)
Вызовите useMemo на верхнем уровне вашего компонента, чтобы кэшировать результат вычисления между перерендерами:
import { useMemo } from 'react';
function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// ...
} Параметры
-
calculateValue: Функция, вычисляющая значение, которое вы хотите кэшировать. Она должна быть чистой, не принимать аргументов и возвращать значение любого типа. React вызовет вашу функцию во время первоначального рендера. При последующих рендерах React вернёт то же значение, если значенияdependenciesне изменились с момента последнего рендера. В противном случае он вызоветcalculateValue, вернёт результат и сохранит его для повторного использования. -
dependencies: Список всех реактивных значений, используемых в кодеcalculateValue. Реактивные значения включают props, состояние и все переменные и функции, объявленные непосредственно внутри вашего компонента. Если ваш линтер настроен для React, он проверит, что каждое реактивное значение правильно указано в качестве зависимости. Список зависимостей должен иметь постоянное количество элементов и быть написан в строке, например,[dep1, dep2, dep3]. React сравнит каждую зависимость со своим предыдущим значением, используя сравнениеObject.is.
Возвращаемое значение
При первоначальном рендере useMemo возвращает результат вызова calculateValue без аргументов.
Во время последующих рендеров он либо возвращает уже сохранённое значение из последнего рендера (если зависимости не изменились), либо вызывает calculateValue снова и возвращает результат, который вернула calculateValue.
Ограничения
-
useMemo— это хук, поэтому вы можете вызывать его только на верхнем уровне вашего компонента или собственных хуках. Вы не можете вызывать его внутри циклов или условных операторов. Если вам это нужно, создайте новый компонент и перенесите состояние в него. - В режиме Strict Mode React вызовет вашу функцию вычисления дважды, чтобы помочь вам найти случайные нечистоты. Это поведение только для разработки и не влияет на производство. Если ваша функция вычисления чистая (как должно быть), это не должно повлиять на вашу логику. Результат одного из вызовов будет проигнорирован.
- React не удалит кэшированное значение, если нет особой причины для этого. Например, в режиме разработки React удаляет кэш при редактировании файла вашего компонента. Как в разработке, так и в производстве React удалит кэш, если ваш компонент приостановится во время первоначальной установки. В будущем React может добавить больше функций, использующих удаление кэша — например, если React добавит встроенную поддержку виртуализированных списков в будущем, будет целесообразно удалить кэш для элементов, которые выходят за пределы области видимости виртуализированной таблицы. Это должно быть нормально, если вы полагаетесь на
useMemoтолько как на оптимизацию производительности. В противном случае переменная состояния или переменная состояния или ссылка могут быть более подходящими.
Примечание
Кэширование возвращаемых значений таким образом также известно как мемоизация, поэтому этот хук называется useMemo.
Использование
Пропуск дорогостоящих перерасчётов
Чтобы кэшировать результат вычисления между перерендерами, оберните его в вызов useMemo на верхнем уровне вашего компонента:
import { useMemo } from 'react';
function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
} Вам нужно передать две вещи в useMemo:
- Функцию вычисления, которая не принимает аргументов, например,
() =>, и возвращает то, что вы хотели вычислить. - Список зависимостей, включающий все значения в вашем компоненте, которые используются внутри вычисления.
При первоначальном рендере значение, которое вы получите от useMemo , будет результатом вызова вашей функции вычисления.
При каждом последующем рендере React сравнит зависимости с зависимостями, которые вы передали при последнем рендере. Если ни одна из зависимостей не изменилась (сравнение с Object.is), useMemo вернёт уже вычисленное ранее значение. В противном случае React повторно выполнит вычисление и вернёт новое значение.
Другими словами, useMemo кэширует результат вычисления между перерендерами до тех пор, пока не изменятся его зависимости.
Давайте разберём пример, чтобы увидеть, когда это полезно.
По умолчанию React повторно выполнит весь код вашего компонента при каждом перерендере. Например, если этот TodoList обновляет своё состояние или получает новые свойства от родительского компонента, функция filterTodos будет повторно выполнена:
function TodoList({ todos, tab, theme }) {
const visibleTodos = filterTodos(todos, tab);
// ...
} Обычно это не проблема, потому что большинство вычислений очень быстрые. Однако, если вы фильтруете или преобразуете большой массив или выполняете какое-то дорогостоящее вычисление, вы можете захотеть пропустить его повторное выполнение, если данные не изменились. Если и todos и tab такие же, как и при последнем рендере, обернув вычисление в useMemo , как раньше, позволяет повторно использовать уже вычисленное значение visibleTodos.
Этот тип кэширования называется мемоизацией.
Примечание
Вы должны полагаться на useMemo только как на оптимизацию производительности. Если ваш код не работает без него, найдите основную проблему и исправьте её сначала. Затем вы можете добавить useMemo для повышения производительности.
Подробное рассмотрение
Как определить, является ли вычисление дорогостоящим?
В общем случае, если вы не создаёте или не перебираете тысячи объектов, это, вероятно, не дорогостоящее вычисление. Если вы хотите получить больше уверенности, вы можете добавить запись в консоль для измерения времени, затраченного на определённый фрагмент кода:
console.time('filter array');
const visibleTodos = filterTodos(todos, tab);
console.timeEnd('filter array');
Выполните взаимодействие, которое вы измеряете (например, введите текст в поле ввода). Тогда вы увидите записи, такие как filter array: 0.15ms в вашей консоли. Если общее зафиксированное время составляет значительную величину (скажем, 1ms или более), может иметь смысл выполнить мемоизацию этого вычисления. В качестве эксперимента вы можете обернуть вычисление в useMemo , чтобы проверить, не уменьшилось ли общее зафиксированное время для данного взаимодействия или нет:
console.time('filter array');
const visibleTodos = useMemo(() => {
return filterTodos(todos, tab); // Skipped if todos and tab haven't changed
}, [todos, tab]);
console.timeEnd('filter array');
useMemo не сделает первый рендер быстрее. Он только помогает пропустить ненужную работу при обновлениях.
Помните, что ваш компьютер, вероятно, быстрее, чем компьютеры ваших пользователей, поэтому неплохо протестировать производительность с искусственным замедлением. Например, Chrome предлагает функцию уменьшения частоты кадров для этого.
Также обратите внимание, что измерение производительности в режиме разработки не даст вам самых точных результатов. (Например, когда включён режим Strict Mode, вы увидите рендер каждого компонента дважды, а не один раз.) Чтобы получить наиболее точные измерения времени, скомпилируйте приложение для производства и протестируйте его на устройстве, подобном тому, которое имеют ваши пользователи.
Глубокое погружение
Нужно ли добавлять useMemo везде?
Если ваше приложение похоже на этот сайт, и большинство взаимодействий являются грубыми (например, замена страницы или всего раздела), мемоизация обычно не нужна. С другой стороны, если ваше приложение больше похоже на редактор рисунков, и большинство взаимодействий являются мелкозернистыми (например, перемещение фигур), то вы можете обнаружить, что мемоизация очень полезна.
Оптимизация с помощью useMemo полезна только в нескольких случаях:
- Вычисление, которое вы помещаете в
useMemoзаметно медленное, а его зависимости редко изменяются. - Вы передаёте его в качестве свойства компоненту, обернутому в
memo. Вы хотите пропустить повторное отрисовку, если значение не изменилось. Мемоизация позволяет вашему компоненту перерисовываться только тогда, когда зависимости не совпадают. - Передаваемое вами значение впоследствии используется как зависимость какого-либо крючка. Например, возможно, другое
useMemoвычисление значения зависит от него. Или, возможно, вы зависите от этого значения отuseEffect.
В других случаях нет никакой выгоды от обертывания вычисления в useMemo. Также нет существенного вреда от этого, поэтому некоторые команды предпочитают не задумываться над отдельными случаями и использовать мемоизацию по возможности. Недостатком этого подхода является то, что код становится менее читабельным. Кроме того, не вся мемоизация эффективна: одно значение, которое «всегда новое», достаточно, чтобы сломать мемоизацию для всего компонента.
На практике вы можете сделать множество мемоизаций ненужными, следуя нескольким принципам:
- Когда компонент визуально обёртывает другие компоненты, позвольте ему принимать JSX в качестве дочерних элементов. Таким образом, когда компонент-обёртка обновляет своё собственное состояние, React знает, что его дочерние элементы не нужно перерисовывать.
- Предпочитайте локальное состояние и не поднимайте состояние выше, чем это необходимо. Например, не храните временное состояние, такое как формы и то, наведён ли курсор на элемент, в верхней части вашего дерева или в библиотеке глобального состояния.
- Сохраняйте свою логику отрисовки чистой. Если повторное отрисовка компонента вызывает проблему или производит какие-либо заметные визуальные артефакты, это ошибка в вашем компоненте! Исправьте ошибку вместо добавления мемоизации.
- Избегайте ненужных эффектов, обновляющих состояние. Большинство проблем с производительностью в приложениях React вызваны цепочками обновлений, исходящими от эффектов, которые заставляют ваши компоненты перерисовываться снова и снова.
- Постарайтесь удалить ненужные зависимости из ваших эффектов. Например, вместо мемоизации часто проще перенести какой-либо объект или функцию внутри эффекта или за пределы компонента.
Если какое-то конкретное взаимодействие по-прежнему кажется медленным, используйте инструмент профилирования React Developer Tools, чтобы увидеть, какие компоненты больше всего выиграют от мемоизации, и добавьте мемоизацию, где это необходимо. Эти принципы делают ваши компоненты проще отлаживать и понимать, поэтому следовать им в любом случае стоит. В долгосрочной перспективе мы исследуем автоматическую мелкозернистую мемоизацию, чтобы решить эту проблему раз и навсегда.
Пропуск повторной отрисовки компонентов
В некоторых случаях useMemo также может помочь оптимизировать производительность повторной отрисовки дочерних компонентов. Чтобы проиллюстрировать это, предположим, что этот TodoList компонент передает visibleTodos в качестве свойства дочернему List компоненту:
export default function TodoList({ todos, tab, theme }) {
// ...
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
} Вы заметили, что переключение theme свойства на мгновение замораживает приложение, но если вы удалите <List /> из вашего JSX, оно работает быстро. Это говорит вам о том, что стоит попробовать оптимизировать List компонент.
По умолчанию, когда компонент перерисовывается, React рекурсивно перерисовывает все его дочерние элементы. Вот почему, когда TodoList перерисовывается с другим theme, компонент List также перерисовывается. Это нормально для компонентов, для перерисовки которых не требуется много вычислений. Но если вы убедились, что повторная отрисовка занимает много времени, вы можете указать List пропустить повторную отрисовку, когда его свойства совпадают со свойствами при предыдущей отрисовке, обернув его в memo:
import { memo } from 'react';
const List = memo(function List({ items }) {
// ...
}); С этим изменением List пропустит повторную отрисовку, если все его свойства одинаковы, как при предыдущей отрисовке. Именно здесь кэширование вычисления становится важным! Представьте, что вы вычисляете visibleTodos без useMemo:
export default function TodoList({ todos, tab, theme }) {
// Every time the theme changes, this will be a different array...
const visibleTodos = filterTodos(todos, tab);
return (
<div className={theme}>
{/* ... so List's props will never be the same, and it will re-render every time */}
<List items={visibleTodos} />
</div>
);
} В приведенном выше примере функция filterTodos всегда создаёт разный массив, подобно тому, как объектная запись {} всегда создаёт новый объект. Обычно это не проблема, но это означает, что свойства List никогда не будут одинаковыми, и ваша оптимизация memo не сработает. Именно здесь useMemo оказывается полезной:
export default function TodoList({ todos, tab, theme }) {
// Tell React to cache your calculation between re-renders...
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab] // ...so as long as these dependencies don't change...
);
return (
<div className={theme}>
{/* ...List will receive the same props and can skip re-rendering */}
<List items={visibleTodos} />
</div>
);
} Оборачивая вычисление visibleTodos в useMemo, вы обеспечиваете, что оно имеет одинаковое значение между перерисовками (до тех пор, пока зависимости не изменятся). Вам не нужно обертывать вычисление в useMemo, если вы не делаете этого по какой-то конкретной причине. В этом примере причиной является то, что вы передаёте его компоненту, обернутому в memo, и это позволяет ему пропустить повторную отрисовку. Есть и другие причины добавить useMemo, которые подробно описаны на этой странице.
Глубокое погружение
Мемоизация отдельных узлов JSX
Вместо того чтобы обертывать List в memo, вы можете обернуть сам узел JSX <List /> в useMemo:
export default function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
const children = useMemo(() => <List items={visibleTodos} />, [visibleTodos]);
return (
<div className={theme}>
{children}
</div>
);
}
Поведение было бы таким же. Если visibleTodos не изменились, List не будет перерисовываться.
Узел JSX, такой как <List items={visibleTodos} /> — это объект, похожий на { type: List, props: { items: visibleTodos } }. Создание этого объекта очень недорого, но React не знает, одинаково ли его содержимое с предыдущим или нет. Вот почему по умолчанию React перерисует компонент List.
Однако, если React видит точно такой же JSX, как и во время предыдущей отрисовки, он не будет пытаться перерисовать ваш компонент. Это потому, что узлы JSX являются неизменяемыми. Объект узла JSX не мог измениться со временем, поэтому React знает, что безопасно пропустить повторную отрисовку. Однако для этого узел должен быть фактически тем же объектом, а не просто выглядеть одинаково в коде. Именно это делает useMemo в этом примере.
Ручное обертывание узлов JSX в useMemo неудобно. Например, вы не можете сделать это условно. Вот почему обычно вы оборачиваете компоненты с memo вместо обертывания узлов JSX.
Предотвращение слишком частой активации эффекта
Иногда вам может потребоваться использовать значение внутри эффекта:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const options = {
serverUrl: 'https://localhost:1234',
roomId: roomId
}
useEffect(() => {
const connection = createConnection(options);
connection.connect();
// ... Это создаёт проблему. Каждое реактивное значение должно быть объявлено в качестве зависимости вашего эффекта. Однако, если вы объявляете options в качестве зависимости, это приведёт к тому, что ваш эффект постоянно будет подключаться к чату:
useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]); // 🔴 Problem: This dependency changes on every render
// ... Чтобы решить эту проблему, вы можете обернуть объект, который нужно вызвать из эффекта, в useMemo:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const options = useMemo(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // ✅ Only changes when roomId changes
useEffect(() => {
const options = createOptions();
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]); // ✅ Only changes when createOptions changes
// ... Это гарантирует, что объект options будет одинаковым между перерисовками, если useMemo возвращает кэшированный объект.
Однако, поскольку useMemo является оптимизацией производительности, а не семантической гарантией, React может выбросить кэшированное значение, если есть конкретная причина для этого. Это также заставит эффект повторно активироваться, поэтому ещё лучше устранить необходимость в зависимости функции, переместив ваш объект внутри эффекта:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
const options = { // ✅ No need for useMemo or object dependencies!
serverUrl: 'https://localhost:1234',
roomId: roomId
}
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Only changes when roomId changes
// ... Теперь ваш код проще и не нуждается в useMemo. Узнайте больше о удалении зависимостей эффекта.
Мемоизация зависимости другого крючка
Предположим, у вас есть вычисление, которое зависит от объекта, созданного непосредственно в теле компонента:
function Dropdown({ allItems, text }) {
const searchOptions = { matchMode: 'whole-word', text };
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // 🚩 Caution: Dependency on an object created in the component body
// ... Зависимость от такого объекта лишает мемоизации смысла. Когда компонент перерисовывается, весь код, находящийся непосредственно внутри тела компонента, выполняется снова. Строки кода, создающие объект searchOptions, также будут выполняться при каждой перерисовке. Поскольку searchOptions является зависимостью вашего вызова useMemo, и оно отличается каждый раз, React понимает, что зависимости различны, и пересчитывает searchItems каждый раз.
Чтобы исправить это, вы могли бы запомнить объект searchOptions сам по себе перед его передачей в качестве зависимости:
function Dropdown({ allItems, text }) {
const searchOptions = useMemo(() => {
return { matchMode: 'whole-word', text };
}, [text]); // ✅ Only changes when text changes
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // ✅ Only changes when allItems or searchOptions changes
// ... В примере выше, если text не изменился, объект searchOptions также не изменится. Однако ещё лучшим решением является перемещение объявления объекта searchOptions внутри функции вычисления useMemo:
function Dropdown({ allItems, text }) {
const visibleItems = useMemo(() => {
const searchOptions = { matchMode: 'whole-word', text };
return searchItems(allItems, searchOptions);
}, [allItems, text]); // ✅ Only changes when allItems or text changes
// ... Теперь ваше вычисление зависит от text напрямую (которое является строкой и не может «случайно» измениться).
Мемоизация функции
Предположим, компонент Form обернут в memo. Вы хотите передать в него функцию в качестве свойства:
export default function ProductPage({ productId, referrer }) {
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}
return <Form onSubmit={handleSubmit} />;
} Так же, как {} создаёт разные объекты, объявления функций, подобные function() {} и выражения, такие как () => {}, генерируют разную функцию при каждой перерисовке. Само по себе создание новой функции не является проблемой. Этого не следует избегать! Однако, если компонент Form запоминается, предполагается, что вы хотите пропустить его перерисовку, когда никакие свойства не изменились. Свойство, которое всегда отличается, лишает мемоизацию смысла.
Чтобы запомнить функцию с помощью useMemo, вашей функции вычисления нужно возвращать другую функцию:
export default function Page({ productId, referrer }) {
const handleSubmit = useMemo(() => {
return (orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
};
}, [productId, referrer]);
return <Form onSubmit={handleSubmit} />;
} Это выглядит громоздко! Запоминание функций достаточно распространено, поэтому в React есть встроенный хук специально для этого. Оберните ваши функции useCallback вместо useMemo, чтобы избежать необходимости написания дополнительной вложенной функции:
export default function Page({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}, [productId, referrer]);
return <Form onSubmit={handleSubmit} />;
} Два приведённых выше примера полностью эквивалентны. Единственное преимущество useCallback заключается в том, что оно позволяет избежать написания дополнительной вложенной функции внутри. Оно ничего больше не делает. Подробнее о useCallback.
Устранение неполадок
Мой расчет выполняется дважды при каждом перерендере
В строгом режиме React вызовет некоторые ваши функции дважды вместо одного раза:
function TodoList({ todos, tab }) {
// This component function will run twice for every render.
const visibleTodos = useMemo(() => {
// This calculation will run twice if any of the dependencies change.
return filterTodos(todos, tab);
}, [todos, tab]);
// ... Это ожидаемо и не должно сломать ваш код.
Это поведение, используемое только в режиме разработки, помогает вам поддерживать компоненты чистыми. React использует результат одного из вызовов и игнорирует результат другого вызова. До тех пор, пока ваши компонентные и вычислительные функции являются чистыми, это не должно повлиять на вашу логику. Однако, если они случайно являются нечистыми, это помогает вам заметить и исправить ошибку.
Например, эта нечистая функция вычисления изменяет массив, полученный в качестве свойства:
const visibleTodos = useMemo(() => {
// 🚩 Mistake: mutating a prop
todos.push({ id: 'last', text: 'Go for a walk!' });
const filtered = filterTodos(todos, tab);
return filtered;
}, [todos, tab]); React дважды вызовет вашу функцию, поэтому вы заметите, что задача добавлена дважды. Ваш расчет не должен изменять существующие объекты, но изменение новых объектов, созданных во время расчета, допустимо. Например, если функция filterTodos всегда возвращает разный массив, вы можете изменить этот массив:
const visibleTodos = useMemo(() => {
const filtered = filterTodos(todos, tab);
// ✅ Correct: mutating an object you created during the calculation
filtered.push({ id: 'last', text: 'Go for a walk!' });
return filtered;
}, [todos, tab]); Прочитайте поддержание чистоты компонентов, чтобы узнать больше о чистоте.
Также ознакомьтесь с руководствами по обновлению объектов и обновлению массивов без мутации.
Мой вызов useMemo должен возвращать объект, но возвращает undefined
Этот код не работает:
// 🔴 You can't return an object from an arrow function with () => {
const searchOptions = useMemo(() => {
matchMode: 'whole-word',
text: text
}, [text]); В JavaScript, () => { начинает тело стрелочной функции, поэтому фигурная скобка { не является частью вашего объекта. Вот почему он не возвращает объект и приводит к ошибкам. Вы можете исправить это, добавив скобки, как ({ и }):
// This works, but is easy for someone to break again
const searchOptions = useMemo(() => ({
matchMode: 'whole-word',
text: text
}), [text]); Однако это всё ещё запутанно и слишком легко для кого-то сломать, удалив скобки.
Чтобы избежать этой ошибки, напишите оператор return явно:
// ✅ This works and is explicit
const searchOptions = useMemo(() => {
return {
matchMode: 'whole-word',
text: text
};
}, [text]); Каждый раз, когда мой компонент перерисовывается, вычисление в useMemo выполняется повторно
Убедитесь, что вы указали массив зависимостей в качестве второго аргумента!
Если вы забудете массив зависимостей, useMemo будет повторно выполнять вычисление каждый раз:
function TodoList({ todos, tab }) {
// 🔴 Recalculates every time: no dependency array
const visibleTodos = useMemo(() => filterTodos(todos, tab));
// ... Вот исправленная версия, передающая массив зависимостей как второй аргумент:
function TodoList({ todos, tab }) {
// ✅ Does not recalculate unnecessarily
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ... Если это не поможет, проблема заключается в том, что по крайней мере одна из ваших зависимостей отличается от предыдущего рендера. Вы можете отладить эту проблему, вручную выведя свои зависимости в консоль:
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
console.log([todos, tab]); Затем вы можете щелкнуть правой кнопкой мыши по массивам из разных перерисовываний в консоли и выбрать «Сохранить как глобальную переменную» для обоих. Предполагая, что первый был сохранен как 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 ... Когда вы найдете зависимость, которая нарушает запоминание, найдите способ её удалить или запомните её тоже.
Мне нужно вызвать useMemo для каждого элемента списка в цикле, но это запрещено
Предположим, что компонент Chart обернут в memo. Вы хотите пропустить перерисовку каждого Chart в списке, когда компонент ReportList перерисовывается. Однако вы не можете вызвать useMemo в цикле:
function ReportList({ items }) {
return (
<article>
{items.map(item => {
// 🔴 You can't call useMemo in a loop like this:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure key={item.id}>
<Chart data={data} />
</figure>
);
})}
</article>
);
} Вместо этого извлеките компонент для каждого элемента и запомните данные для отдельных элементов:
function ReportList({ items }) {
return (
<article>
{items.map(item =>
<Report key={item.id} item={item} />
)}
</article>
);
}
function Report({ item }) {
// ✅ Call useMemo at the top level:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure>
<Chart data={data} />
</figure>
);
} В качестве альтернативы вы можете удалить useMemo и вместо этого обернуть сам Report в memo. Если свойство item не меняется, Report пропустит перерисовку, поэтому Chart также пропустит перерисовку:
function ReportList({ items }) {
// ...
}
const Report = memo(function Report({ item }) {
const data = calculateReport(item);
return (
<figure>
<Chart data={data} />
</figure>
);
});
© 2013–present Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://18.react.dev/reference/react/useMemo