UseMemo против useEffect + useState

Есть ли преимущества в использовании useMemo (например, для интенсивного вызова функции) вместо комбинации useEffect и useState?

Вот два пользовательских хука, которые на первый взгляд работают одинаково, за исключением того, что возвращаемое значение useMemo равно null при первом рендеринге:

UseMemo против useEffect + useState

useEffect и useState

import { expensiveCalculation } from "foo";

function useCalculate(someNumber: number): number {
  const [result, setResult] = useState<number>(null);

  useEffect(() => {
    setResult(expensiveCalculation(someNumber));
  }, [someNumber]);

  return result;
}

useMemo

import { expensiveCalculation } from "foo";

function useCalculateWithMemo(someNumber: number): number {
    return useMemo(() => {
        return expensiveCalculation(someNumber);
    }, [someNumber]);
};

Оба рассчитывают результат каждый раз, когда их параметр someNumber изменяется, где вмешивается запоминание useMemo?

Первый будет null на первом рендере, а второй нет?

Jonas Wilms 07.05.2019 21:08
Есть ли преимущества в использовании useMemo (например, для интенсивного вызова функции) - да. Вы используете крючок, который был разработан специально для этой цели. Пример, который вы перечислили, является наиболее распространенным примером использования useMemo в реальном мире.
Estus Flask 07.05.2019 21:42
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
120
2
53 954
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

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

Mark Adamson 16.09.2019 17:15

@MarkAdamson Это применимо к каждому сценарию, в котором вы используете useEffect для синхронного вычисления значения и установки его с помощью useState. Это не применимо к асинхронным ситуациям, например, когда вы хотите показать счетчик загрузки.

Retsam 16.09.2019 21:28

Я думаю, что useEffect может быть полезен и в некоторых длительных синхронных сценариях. Проверьте песочницу ниже. Загрузка занимает 5 секунд из-за того, что useMemo удерживает поток рендеринга во время выполнения длинного вычисления, в то время как useEffect/useState может отображать «спиннер» во время выполнения вычисления, поэтому рендеринг не задерживается: codeandbox.io/s/usememo-vs-useeffect-usestate-ye6qm @Retsam

Mark Adamson 17.09.2019 09:06

помимо оптимизации, я использую useMemo вместо шаблона useState + useEffect, потому что отладка сложнее с большим количеством рендеров.

ecoe 21.09.2019 14:45

Стоит отметить, что в документах React API упоминается, что useMemo не гарантирует, что мемоизированная функция не будет выполняться снова, если зависимости не изменятся, потому что React может в будущем отказаться от кеша для повышения производительности. Поэтому, если у мемоизированной функции есть какие-то побочные эффекты, может быть разумнее использовать собственный хук.

M Miller 19.01.2020 20:20

Почему он снова отобразит «1», когда numberProp изменится на 2

Abhi 15.06.2020 08:10

@Abhi Изменение реквизита вызывает повторный рендеринг. Но визуализируемое значение основано на состоянии [result, setResult], и setResult не будет вызываться до тех пор, пока не запустится useEffect, что происходит после рендеринга.

Retsam 15.06.2020 23:37
useState может получить функцию для вычисления его начального значения. Итак, если нет необходимости пересчитывать мемоизированное значение на основе реквизита, то useState и useMemo равны? const [x] = useState(() => calcX()) эквивалентно const x = useMemo(() => calcX(), [])?
Dmitry Davydov 04.07.2020 08:19

@DmitryDavydov Я думаю, что это правда, но если вычисление calcX() не зависит ни от какого состояния или реквизита вашего компонента, вы можете вытащить его из компонента как константу и вообще не нужно использовать хук.

Retsam 05.07.2020 17:38

Можем ли мы заключить из этого, что useEffect + useState является правильным решением в ситуациях, когда нужно вычислить async, потому что значение все равно не может быть доступно в текущем рендере? Связанный вопрос.

bluenote10 08.11.2020 23:52

@ bluenote10 Да, обычно нет смысла запоминать само обещание, поэтому вместо этого вы хотите обновить состояние на основе результата обещания.

Retsam 11.11.2020 19:25

Я вижу, как писал выше, что пакет useMemo намного лучше, когда есть чрезвычайно дорогие вычисления, которые не зависят от состояний или каких-либо побочных эффектов, так что больше для случая чистых «функций».

Carmine Tambascia 16.02.2022 19:12

@MarkAdamson, наоборот, лучше использовать useEffect + useState, когда есть необходимость в асинхронном коде, который по своей природе является побочным эффектом, то есть он происходит снаружи, и вы точно не знаете, когда, как здесь уже указывали другие stackoverflow.com/questions/61751728/…

Carmine Tambascia 16.02.2022 22:41

Спасибо @CarmineTambascia, я давно не публиковал это, поэтому не помню контекст. Но я не думаю, что наши заявления противоречат друг другу. Я определял время, когда useEffect может быть полезен для синхронных действий, которые не имеют побочных эффектов по сравнению с usememo. Это по-прежнему так, что useEffect предназначен и предназначен для помощи с побочными эффектами, как вы говорите.

Mark Adamson 18.02.2022 00:43

Я думаю, что есть два основных момента, которые вы должны учитывать при выборе между ними.

  1. Время вызова функции.

useEffect вызывается после рендеринга компонента, поэтому вы можете получить доступ к DOM из него. Например, это важно, если вы хотите получить доступ к элементам DOM через ссылки.

  1. Семантические гарантии.

useEffect гарантирует, что не сработает, если зависимости не изменились. useMemo таких гарантий не дает.

Как сказано в Реагировать на документацию, вы должны рассматривать useMemo как чистую технику оптимизации. Ваша программа должна продолжать работать правильно, даже если вы замените useMemo обычным вызовом функции.

useEffect + useState можно использовать для управления обновлениями. Даже для разрыва циклических зависимостей и предотвращения бесконечных циклов обновления.

Я бы сказал, что помимо асинхронной природы, может быть какая-то разница в том, как они спроектированы.

useEffect — это коллективный вызов, асинхронный или нет, он собирается после рендеринга всех компонентов.

useMemo — это локальный вызов, который имеет отношение только к этому компоненту. Вы можете просто думать о useMemo как о другом операторе присваивания с преимуществами использования присваивания из последнего обновления.

Это означает, что useMemo является более срочным, а затем useLayoutEffect и последним является useEffect.

Другие вопросы по теме