Когда НЕ следует использовать React memo?

Я недавно играл с Реагировать16.6.0, и мне нравится идея React Memo, но я не смог найти ничего относительно сценариев, которые лучше всего подходят для его реализации.

Документы React (https://reactjs.org/docs/react-api.html#reactmemo), похоже, не предполагают каких-либо последствий от простого добавления его ко всем вашим функциональным компонентам.

Поскольку он проводит поверхностное сравнение, чтобы выяснить, нужно ли повторное рендеринг, возникнет ли когда-нибудь ситуация, когда отрицательно влияет на производительность?

Такая ситуация кажется очевидным выбором для реализации:

// NameComponent.js
import React from "react";
const NameComponent = ({ name }) => <div>{name}</div>;
export default React.memo(NameComponent);

// CountComponent.js
import React from "react";
const CountComponent = ({ count }) => <div>{count}</div>;
export default CountComponent;

// App.js
import React from "react";
import NameComponent from "./NameComponent";
import CountComponent from "./CountComponent";

class App extends Component {
  state = {
    name: "Keith",
    count: 0
  };

  handleClick = e => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <NameComponent name = {this.state.name} />
        <CountComponent count = {this.state.count} />
        <button onClick = {this.handleClick}>Add Count</button>
      </div>
    );
  }
}

Поскольку name никогда не изменится в этом контексте, имеет смысл использовать запоминать.

Но как насчет ситуации, когда реквизит часто меняется?
Что, если я добавлю еще одну кнопку, которая изменит что-то еще в состоянии и запустит повторный рендеринг, имеет ли смысл обернуть CountComponent в памятка, даже если этот компонент по дизайну предназначен для частого обновления?

Думаю, мой главный вопрос - пока все остается чистым, есть ли ситуация, когда функциональный компонент не упаковывается в React Memo?

Если ваш компонент всегда повторно рендерится, он будет каждый раз выполнять ненужную мелкую проверку свойств. Это то же самое, что и PureComponent.

Andy Ray 31.10.2018 01:24

@AndyRay Думаю, меня интересует, где находится порог убывающей отдачи. Если моему функциональному компоненту теоретически необходимо повторно отрисовывать ровно 95% времени, потребует ли стоимость этого повторного рендеринга использование памятки? Я знаю, что это очень специфический сценарий, и я не ищу точных тестов, мне просто было более любопытно, есть ли линия, которую можно провести на песке.

Keith Brewster 31.10.2018 01:34

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

Andy Ray 31.10.2018 01:39

Может быть, более острый вопрос будет: «Когда НЕ следует использовать React memo?». Или «Следует ли вам ВСЕГДА использовать React memo и отказываться от него, только если есть проблемы с перфомансом?».

protoEvangelion 03.11.2018 19:30

@protoEvangelion отличное предложение, я собираюсь обновить заголовок вопроса

Keith Brewster 04.11.2018 19:11
Поведение ключевого слова "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) для оценки ваших знаний,...
102
5
40 572
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Я думаю, что краткий ответ таков: React.memo делает с функциональными компонентами то же, что React.PureComponent делает с компонентами класса. В этом смысле, когда вы используете memo, он будет оценивать, изменились ли свойства этого функционального компонента, если да, то он выполнит возврат функции, в противном случае - нет, избегая повторного рендеринга компонента.

import React, { memo } from 'react';

const Component = () => {
  debugger;
  return (
    <div>Hello World!</div>
  );
};

const MemoComponent = memo(() => {
  debugger;
  return (
    <div>Hello World!</div>
  );
});

Если вы используете Component в качестве дочернего компонента обновляемого контейнера, каждый раз, когда родительские обновления обновляются, он будет повторно визуализироваться (отладчик будет запускаться каждый раз). С другой стороны, если вы используете MemoComponent, он не будет повторно рендерить (отладчик сработает только при первом рендеринге).

В этом примере это происходит из-за того, что у функционального компонента нет реквизита, в случае, если у него есть реквизиты, это произойдет, только если реквизиты изменятся.

Это не отвечает на вопрос, который четко задан, когда нет использовать памятку, а не «что такое памятка», это другой вопрос, на который вы ответили.

vsync 04.08.2019 16:31

@vsync Я подумал, объяснив, что это такое, будет достаточно ясно, чтобы понять, когда использовать или нет (что, в конце концов, является выбором разработчика). На мой взгляд, я думаю, что его следует использовать почти все время, я не могу придумать какой-либо конкретной ситуации, в которой я бы посоветовал не использовать его, честно.

Michael 05.08.2019 21:43

Если он действительно должен использоваться все время, и вы не можете придумать какой-либо вариант использования, тогда это должно было быть по умолчанию, и умные люди, создавшие React, выбрали иное, что означает, что для этого есть очень веская причина. .Вот твит от Дэна Абрамова

vsync 05.08.2019 22:27

@vsync Я думаю, вам стоит еще раз прочитать мои комментарии, потому что я думаю, что вы неправильно поняли. Во-первых, я сказал: «Следует использовать почти все время» (ключевое слово «Почти»). Во-вторых, когда я говорю, что не могу придумать ситуацию, чтобы предложить не использовать ее, это мое личное и честное мнение, если у вас есть ситуация, вы должны поделиться ею :)

Michael 06.08.2019 23:23

В-третьих, вернемся к моему первоначальному ответу, в котором я пытался объяснить, что это такое, а что нет, причина, в конце концов, заключается в том, что разработчики решают, использовать его или нет. Когда или нет использовать lodash, например ... при использовании extend PureComponent вместо Component. В конце концов, у всего есть своя цена, но, по моему мнению и личному опыту, у меня не было затрат, которые помешали бы мне использовать памятку на моих компонентах по сравнению с преимуществами ее использования.

Michael 06.08.2019 23:29

PS: Я уважаю Дэна А., но не только потому, что он говорит правду, или не только потому, что он высказывает собственное мнение, это правильно. Почти ни в чем нет абсолютной истины.

Michael 06.08.2019 23:31

Я полностью уважаю ваши усилия в попытке ответить на эту тему. Что касается меня, я пришел сюда из Google, потому что я хочу знать, когда нет использовать memo и прокручивая ответы вниз, нет ответа на фактический вопрос. Я уверен, что существуют сценарии, поэтому просмотр этой ветки с точки зрения поиска в Google бесполезен, в конечном итоге ... Если я когда-нибудь обнаружу сценарий, я вернусь и опубликую его здесь.

vsync 07.08.2019 10:23

"Помните, что функция, переданная в useMemo, запускается во время рендеринга. Не делайте там ничего, что вы обычно не делали бы при рендеринге. Например, побочные эффекты принадлежат useEffect, а не useMemo.

Вы можете полагаться на useMemo как на оптимизацию производительности, а не как на семантическую гарантию. В будущем React может решить «забыть» некоторые ранее запомненные значения и пересчитать их при следующем рендеринге, например чтобы освободить память для закадровых компонентов. Напишите свой код так, чтобы он работал без useMemo, а затем добавьте его для оптимизации производительности. (В редких случаях, когда значение никогда не должно пересчитываться, вы можете лениво инициализировать ссылку.) "

https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies

совершенно не имеет отношения к вопросу. ваш ответ относится к хуку useMemo, который нет следует путать с функцией react.memo

vsync 04.08.2019 16:30
Ответ принят как подходящий

Все компоненты React реализуют метод shouldComponentUpdate(). По умолчанию (компоненты, расширяющие React.Component) всегда возвращает true. Изменение, которое вносит запоминание компонента (через React.memo для функциональных компонентов или расширение React.PureComponent для компонентов класса), представляет собой реализацию метода shouldComponentUpdate(), который выполняет поверхностное сравнение состояния и свойств.

Если посмотреть на документация в методах жизненного цикла компонента, shouldComponentUpdate() - это всегда, вызываемый до того, как произойдет рендеринг, это означает, что мемоизация компонента будет включать это дополнительное поверхностное сравнение при каждом обновлении.

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

Чтобы ответить на ваш вопрос, я не думаю, что существует явное правило, когда вы должны или не должны мемоизировать компоненты, однако я думаю, что следует применять тот же принцип, что и при решении, следует ли вам переопределять shouldComponentUpdate(): найти проблемы с производительностью с помощью предлагаемого инструменты профилирования и определить, нужно ли вам оптимизировать компонент.

Это отличный ответ, и я думаю, вы абсолютно правы в том, что профилирование - необходимый инструмент для определения масштабов ошибок производительности.

Keith Brewster 10.06.2019 17:02

Да, я думаю, что многие функции, которые дает нам React, не предназначены для того, чтобы быть волшебными палочками, это больше инструментов, помогающих решать проблемы в определенных (иногда общих) ситуациях, я рад, что вы нашли мой ответ полезным @KeithBrewster

Ntokozo Zwane 10.06.2019 23:06

Вы должны были упомянуть о последствиях для памяти (память - это не производительность). Я не знаю, почему люди думают, что стоимость React.memo - это просто дополнительное поверхностное сравнение. Когда на самом деле это «дополнительное поверхностное сравнение» + «дополнительный снимок рендеринга для каждого экземпляра компонента». По крайней мере, для функциональных компонентов.

Ivan Kleshnin 25.06.2019 10:53

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

jarora 11.02.2021 13:20

@IvanKleshnin интересный момент! Мне было бы интересно проработать второй оператор «дополнительный снимок рендеринга для каждого экземпляра компонента».

Nick Mitchell 19.07.2021 03:29

@NickMitchell Я имел в виду, что внутреннее устройство React.memo хранит значения свойств в памяти (чтобы иметь возможность сравнивать с новыми значениями свойств). Если эти данные достаточно велики, они могут иметь ощутимый след.

Ivan Kleshnin 19.07.2021 14:59

Поправьте меня, если я ошибаюсь или неправильно понимаю, но я считаю, что React.Component не реализует shouldComponentUpdate (). Скорее, только React.PureComponent реализует shouldComponentUpdate (). Источник: reactjs.org/docs/react-api.html#reactpurecomponent.

Brendan 31.08.2021 01:54

Тот же вопрос имеет ответ от markerikson в системе отслеживания проблем React GitHub. Он получил больше одобрения, чем ответов здесь.

I would assume that the same general advice applies for React.memo as it does for shouldComponentUpdate and PureComponent: doing comparisons does have a small cost, and there's scenarios where a component would never memoize properly (especially if it makes use of props.children). So, don't just automatically wrap everything everywhere. See how your app behaves in production mode, use React's profiling builds and the DevTools profiler to see where bottlenecks are, and strategically use these tools to optimize parts of the component tree that will actually benefit from these optimizations.

Идея состоит в том, чтобы избежать использования мемоизации для данных, которые могут изменяться очень часто. Как указано в блоге, это также включает обратные вызовы, которые зависят от таких типов данных. Например, такие функции как

<Foo onClick = {() => handle(visitCount)}/>

Мне очень понравилось это упрощенное чтение. Примеры отличные. https://dmitripavlutin.com/use-react-memo-wisely/

Is there ever going to be a situation that negatively impacts performance?

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

Во многих случаях это не нужно. Чтобы попробовать с критически важным для производительности компонентом, сначала сделайте некоторые меры, добавьте мемоизацию, а затем снова измерьте, чтобы увидеть, стоит ли этого добавленная сложность.

Сколько стоит React.memo?

Мемоизированный компонент сравнивает старый реквизит с новостным, чтобы решить, нужно ли повторно рендерить - каждый цикл рендеринга.
Простой компонент не заботится о нем и просто отображает его после изменения свойств / состояния в родительском элементе.

Взгляните на реализацию React shallowEqual, которая вызывается в updateMemoComponent.

Когда НЕ используйте React memo?

Нет жестких правил. Вещи, негативно влияющие на React.memo:

  1. компонент часто перерисовывается с пропсами, которые все равно изменились
  2. компонент дешев для повторного рендеринга
  3. функция сравнения стоит дорого

Объявление 1: В этом случае React.memo не может предотвратить повторный рендеринг, но ему потребовались дополнительные вычисления. Объявление 2: дополнительная стоимость сравнения не стоит того для "простого" компонента с точки зрения затрат на рендеринг, согласование, изменение модели DOM и побочные эффекты.
Объявление 3: чем больше реквизита, тем больше расчетов. Вы также можете передать более сложный пользовательский компаратор.

Когда дополнить React.memo?

Он проверяет только свойства, но не изменения контекста или состояния изнутри. React.memo также бесполезен, если мемоизированный компонент имеет непримитивный children. useMemo может дополнять memo здесь, например:

// inside React.memo component
const ctxVal = useContext(MyContext); // context change normally trigger re-render
return useMemo(() => <Child />, [customDep]) // prevent re-render of children

Как может повторный рендеринг всего HTML-компонента быть дешевле, чем буквальное сравнение значений, что, возможно, является одной из самых быстрых операций во всех языках программирования? это не правильно на многих уровнях. Хотя это правда, что бездумное запоминание компонентов может буквально занять больше циклов процессора, но это никак не сравнится с каким-либо повторным рендерингом, если вы не сравните буквально тысячи параметров одновременно, что маловероятно.

vasilevich 06.09.2020 19:34

Что ж, моя основная мысль: учитывая, что ваш компонент в большинстве случаев все равно изменил реквизиты в случае родительского повторного рендеринга, React.memo должен выполнять больше работы, чем простой компонент (согласование различий в свойствах а также). Я лично также не являюсь поклонником преждевременная оптимизация - эти изменения всегда имеют свою стоимость (например, стоимость добавленной абстракции / сложности) и должны быть оправданы.

ford04 06.09.2020 20:31

Также интересно: в связанной статье худшая производительность был испытан и измерен после установки PureComponent aka React.memo для функций на каждом компоненте.

ford04 07.09.2020 10:35

Вы правы в большинстве своих пунктов, и я согласен со всем, кроме дешевизны повторного рендеринга. очевидно, что для компонента, который ВСЕГДА повторно обрабатывается, запоминание бесполезно и плохо сказывается на производительности. Я просто считаю, что самое прекрасное в фреймворках React и React-kind - это идея виртуального дома, основная цель которого - предотвратить ненужные дорогостоящие манипуляции с реальным DOM. Так что, помимо прочего, это своего рода суть фреймворка. поверхностное сравнение подходит не для каждого сценария, но все же является хорошей идеей для многих сценариев.

vasilevich 07.09.2020 14:19

Вы всегда должны использовать React.memo ЛИТЕРАЛЬНО, так как сравнение дерева, возвращаемого Компонентом, всегда дороже, чем сравнение пары свойств props.

Так что никого не слушайте и заверните ВСЕ функциональные компоненты в React.memo. React.memo изначально предназначался для встраивания в ядро ​​функциональных компонентов, но по умолчанию он не используется из-за потери обратной совместимости. (Поскольку он сравнивает объект поверхностно, и вы МОЖЕТЕ использовать вложенные свойства подобъекта в компоненте) =)

Вот и все, это ЕДИНСТВЕННАЯ ПРИЧИНА, почему React не использует memo автоматически. знак равно

Фактически, они могут сделать версию 17.0.0, которая НАРУШИТ обратную совместимость и сделает React.memo значением по умолчанию, а также сделает какую-то функцию для отмены этого поведения, например React.deepProps =)

Перестаньте слушать теоретиков, ребята =) Правило простое:

Если ваш компонент использует ГЛУБОКОЕ СРАВНЕНИЕ PROPS, тогда не используйте memo, иначе ВСЕГДА используйте его, сравнение ДВУХ ОБЪЕКТОВ ВСЕГДА ДЕШЕВЛЕ, чем вызов React.createElement() и сравнение двух деревьев, создание FiberNodes и так далее.

Теоретики говорят о том, чего сами не знают, не анализировали код реакции, не понимают FRP и не понимают, что советуют =)

P.S. если ваш компонент использует опору children, React.memo не будет работать, потому что опора children всегда создает новый массив. Но об этом лучше не заморачиваться, и даже такие компоненты ТАКЖЕ должны быть завернуты в React.memo, так как вычислительные ресурсы ничтожны.

Есть опровержения по этому поводу?

anonym 24.08.2020 05:13

@anonym думает о React.memo как о useEffect (Component, [в зависимости ...]) Только в случае useEffect в документации изначально говорилось, что значения массива сравниваются поверхностно =), а в случае props это не было заявлено, это причина, по которой React.memo не включен по умолчанию =) Кстати, я являюсь одним из разработчиков реакции.

Maxmaxmaximus 31.08.2020 12:04

@anonym У нас была идея, что React.memo не остановит рендеринг своих дочерних элементов, но дети также будут проверяться на предмет изменений свойств. Теперь React.memo блокирует рендеринг всей ветки своего дерева, это также одна из причин, по которой он не включен по умолчанию. Уловка в том, что у реактивности есть такое понятие, как «зерно реактивности»; у каждого зерна есть зависимости. Зависимости сравниваются, и если они не изменились, зернистость не пересчитывается. Это стандартные оптимизации FRP (React - одна из реализаций FRP)

Maxmaxmaximus 31.08.2020 12:12

@anonym Хотя я не смотрел код реакции с момента добавления волокон, мне нужно посмотреть, блокирует ли memo повторную проверку зависимостей дочерних компонентов (реквизита). Но суть не сильно меняется. Реактивность - это «императивный декларативный» =), это фрагменты императивного кода, которые повторно выполняются при изменении их зависимостей. отсюда и слово activation и REactivation (когда зависимости изменились) =), в общем, React.memo пока надо писать вручную (позже напишу плагин babel)

Maxmaxmaximus 31.08.2020 12:20

@anonym Я считаю, что единственное предостережение - на случай, если ваш реквизит перегружен памятью. Мемоизация означает сохранение свойств в памяти потенциально навсегда, то есть до следующего повторного рендеринга, где необходимо сохранить новые объекты, потенциально занимающие много памяти. Эта память будет освобождена только тогда, когда компонент будет размонтирован. Это компромисс между скоростью и памятью.

guitarino 15.04.2021 05:13

Этот ответ просто неверен и вводит в заблуждение. На самом деле очень легко представить случаи, когда сравнение свойств будет медленнее, чем сравнение VDOM. Помните, что дерево VDOM - это обычные данные JS. Теперь все сводится к разнице между размерами пропсов и дерева VDOM. Если ваш компонент возвращает относительно большой VDOM и принимает реквизиты с относительно небольшим размером (обычный сценарий), он получит выгоду от React.memo. Если hovewer ваш компонент возвращает относительно небольшой VDOM, а реквизиты относительно большие ... будет быстрее сравнивать VDOM и избегать лишнего сравнения реквизитов.

Ivan Kleshnin 26.04.2021 16:51

Кто-нибудь знает плагин babel, который автоматически обертывает все функциональные компоненты с помощью React.memo?

Martin Kadlec 11.06.2021 18:30

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