Есть ли преимущества в использовании useMemo (например, для интенсивного вызова функции) вместо комбинации useEffect и useState?
Вот два пользовательских хука, которые на первый взгляд работают одинаково, за исключением того, что возвращаемое значение useMemo равно null при первом рендеринге:
import { expensiveCalculation } from "foo";
function useCalculate(someNumber: number): number {
const [result, setResult] = useState<number>(null);
useEffect(() => {
setResult(expensiveCalculation(someNumber));
}, [someNumber]);
return result;
}
import { expensiveCalculation } from "foo";
function useCalculateWithMemo(someNumber: number): number {
return useMemo(() => {
return expensiveCalculation(someNumber);
}, [someNumber]);
};
Оба рассчитывают результат каждый раз, когда их параметр someNumber изменяется, где вмешивается запоминание useMemo?



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


useEffect и setState будут вызывать дополнительные рендеры при каждом изменении: первый рендер будет «отставать» с устаревшими данными, а затем немедленно поставит в очередь дополнительный рендер с новыми данными.
Предположим, у нас есть:
function expensiveCalculation(x) { return x + 1; }; // Maybe I'm running this on a literal potato
Предположим, что someNumber изначально равно 0:
useMemo сразу отображает 1.useEffect выполняет рендеринг null, затем после рендеринга компонента эффект запускается, меняет состояние и ставит в очередь новый рендеринг с 1.Тогда, если мы изменим someNumber на 2:
useMemo запускается и 3 отображается.useEffect запускается и снова отображает 1, затем срабатывает эффект, и компонент перезапускается с правильным значением 3.Что касается частоты запуска expensiveCalculation, они ведут себя одинаково, но версия useEffect вызывает в два раза больше рендеринга, что плохо сказывается на производительности по другим причинам.
Кроме того, версия useMemo просто чище и читабельнее, ИМО. Он не вводит ненужное изменяемое состояние и имеет меньше движущихся частей.
Так что вам лучше просто использовать useMemo здесь.
Я не на 100% уверен, что это правило применимо к каждому сценарию. Я хотел бы знать и попробовать, что произойдет, если это действительно будет дорогой расчет или если это будет сетевой вызов, когда вы можете захотеть сделать два рендеринга, один со счетчиком и один с окончательным значением. Я предполагаю, что это то, что касается новых функций Suspense, и, возможно, они отлично работают с useMemo. Я пробовал версию, но она мало что мне говорит, поэтому я думаю, что она нуждается в некоторой доработке. codeandbox.io/s/usememo-vs-useeffect-usestate-ye6qm
@MarkAdamson Это применимо к каждому сценарию, в котором вы используете useEffect для синхронного вычисления значения и установки его с помощью useState. Это не применимо к асинхронным ситуациям, например, когда вы хотите показать счетчик загрузки.
Я думаю, что useEffect может быть полезен и в некоторых длительных синхронных сценариях. Проверьте песочницу ниже. Загрузка занимает 5 секунд из-за того, что useMemo удерживает поток рендеринга во время выполнения длинного вычисления, в то время как useEffect/useState может отображать «спиннер» во время выполнения вычисления, поэтому рендеринг не задерживается: codeandbox.io/s/usememo-vs-useeffect-usestate-ye6qm @Retsam
помимо оптимизации, я использую useMemo вместо шаблона useState + useEffect, потому что отладка сложнее с большим количеством рендеров.
Стоит отметить, что в документах React API упоминается, что useMemo не гарантирует, что мемоизированная функция не будет выполняться снова, если зависимости не изменятся, потому что React может в будущем отказаться от кеша для повышения производительности. Поэтому, если у мемоизированной функции есть какие-то побочные эффекты, может быть разумнее использовать собственный хук.
Почему он снова отобразит «1», когда numberProp изменится на 2
@Abhi Изменение реквизита вызывает повторный рендеринг. Но визуализируемое значение основано на состоянии [result, setResult], и setResult не будет вызываться до тех пор, пока не запустится useEffect, что происходит после рендеринга.
useState может получить функцию для вычисления его начального значения. Итак, если нет необходимости пересчитывать мемоизированное значение на основе реквизита, то useState и useMemo равны? const [x] = useState(() => calcX()) эквивалентно const x = useMemo(() => calcX(), [])?
@DmitryDavydov Я думаю, что это правда, но если вычисление calcX() не зависит ни от какого состояния или реквизита вашего компонента, вы можете вытащить его из компонента как константу и вообще не нужно использовать хук.
Можем ли мы заключить из этого, что useEffect + useState является правильным решением в ситуациях, когда нужно вычислить async, потому что значение все равно не может быть доступно в текущем рендере? Связанный вопрос.
@ bluenote10 Да, обычно нет смысла запоминать само обещание, поэтому вместо этого вы хотите обновить состояние на основе результата обещания.
Я вижу, как писал выше, что пакет useMemo намного лучше, когда есть чрезвычайно дорогие вычисления, которые не зависят от состояний или каких-либо побочных эффектов, так что больше для случая чистых «функций».
@MarkAdamson, наоборот, лучше использовать useEffect + useState, когда есть необходимость в асинхронном коде, который по своей природе является побочным эффектом, то есть он происходит снаружи, и вы точно не знаете, когда, как здесь уже указывали другие stackoverflow.com/questions/61751728/…
Спасибо @CarmineTambascia, я давно не публиковал это, поэтому не помню контекст. Но я не думаю, что наши заявления противоречат друг другу. Я определял время, когда useEffect может быть полезен для синхронных действий, которые не имеют побочных эффектов по сравнению с usememo. Это по-прежнему так, что useEffect предназначен и предназначен для помощи с побочными эффектами, как вы говорите.
Я думаю, что есть два основных момента, которые вы должны учитывать при выборе между ними.
useEffect вызывается после рендеринга компонента, поэтому вы можете получить доступ к DOM из него. Например, это важно, если вы хотите получить доступ к элементам DOM через ссылки.
useEffect гарантирует, что не сработает, если зависимости не изменились. useMemo таких гарантий не дает.
Как сказано в Реагировать на документацию, вы должны рассматривать useMemo как чистую технику оптимизации. Ваша программа должна продолжать работать правильно, даже если вы замените useMemo обычным вызовом функции.
useEffect + useState можно использовать для управления обновлениями. Даже для разрыва циклических зависимостей и предотвращения бесконечных циклов обновления.
Я бы сказал, что помимо асинхронной природы, может быть какая-то разница в том, как они спроектированы.
useEffect — это коллективный вызов, асинхронный или нет, он собирается после рендеринга всех компонентов.
useMemo — это локальный вызов, который имеет отношение только к этому компоненту. Вы можете просто думать о useMemo как о другом операторе присваивания с преимуществами использования присваивания из последнего обновления.
Это означает, что useMemo является более срочным, а затем useLayoutEffect и последним является useEffect.
Первый будет
nullна первом рендере, а второй нет?