Я недавно играл с Реагировать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?
@AndyRay Думаю, меня интересует, где находится порог убывающей отдачи. Если моему функциональному компоненту теоретически необходимо повторно отрисовывать ровно 95% времени, потребует ли стоимость этого повторного рендеринга использование памятки? Я знаю, что это очень специфический сценарий, и я не ищу точных тестов, мне просто было более любопытно, есть ли линия, которую можно провести на песке.
Я сомневаюсь, что кто-то работал над этой метрикой, и если вызовы мемоизации являются вашим узким местом, вероятно, есть что-то очень специфическое для потребностей вашего приложения, по которому трудно дать общий совет.
Может быть, более острый вопрос будет: «Когда НЕ следует использовать React memo?». Или «Следует ли вам ВСЕГДА использовать React memo и отказываться от него, только если есть проблемы с перфомансом?».
@protoEvangelion отличное предложение, я собираюсь обновить заголовок вопроса



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


Я думаю, что краткий ответ таков: 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 Я подумал, объяснив, что это такое, будет достаточно ясно, чтобы понять, когда использовать или нет (что, в конце концов, является выбором разработчика). На мой взгляд, я думаю, что его следует использовать почти все время, я не могу придумать какой-либо конкретной ситуации, в которой я бы посоветовал не использовать его, честно.
Если он действительно должен использоваться все время, и вы не можете придумать какой-либо вариант использования, тогда это должно было быть по умолчанию, и умные люди, создавшие React, выбрали иное, что означает, что для этого есть очень веская причина. .Вот твит от Дэна Абрамова
@vsync Я думаю, вам стоит еще раз прочитать мои комментарии, потому что я думаю, что вы неправильно поняли. Во-первых, я сказал: «Следует использовать почти все время» (ключевое слово «Почти»). Во-вторых, когда я говорю, что не могу придумать ситуацию, чтобы предложить не использовать ее, это мое личное и честное мнение, если у вас есть ситуация, вы должны поделиться ею :)
В-третьих, вернемся к моему первоначальному ответу, в котором я пытался объяснить, что это такое, а что нет, причина, в конце концов, заключается в том, что разработчики решают, использовать его или нет. Когда или нет использовать lodash, например ... при использовании extend PureComponent вместо Component. В конце концов, у всего есть своя цена, но, по моему мнению и личному опыту, у меня не было затрат, которые помешали бы мне использовать памятку на моих компонентах по сравнению с преимуществами ее использования.
PS: Я уважаю Дэна А., но не только потому, что он говорит правду, или не только потому, что он высказывает собственное мнение, это правильно. Почти ни в чем нет абсолютной истины.
Я полностью уважаю ваши усилия в попытке ответить на эту тему. Что касается меня, я пришел сюда из Google, потому что я хочу знать, когда нет использовать memo и прокручивая ответы вниз, нет ответа на фактический вопрос. Я уверен, что существуют сценарии, поэтому просмотр этой ветки с точки зрения поиска в Google бесполезен, в конечном итоге ... Если я когда-нибудь обнаружу сценарий, я вернусь и опубликую его здесь.
"Помните, что функция, переданная в 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
Все компоненты React реализуют метод shouldComponentUpdate(). По умолчанию (компоненты, расширяющие React.Component) всегда возвращает true. Изменение, которое вносит запоминание компонента (через React.memo для функциональных компонентов или расширение React.PureComponent для компонентов класса), представляет собой реализацию метода shouldComponentUpdate(), который выполняет поверхностное сравнение состояния и свойств.
Если посмотреть на документация в методах жизненного цикла компонента, shouldComponentUpdate() - это всегда, вызываемый до того, как произойдет рендеринг, это означает, что мемоизация компонента будет включать это дополнительное поверхностное сравнение при каждом обновлении.
Принимая это во внимание, мемоизация компонента делает влияет на производительность, и величина этих эффектов должна определяться путем профилирования вашего приложения и определения того, работает ли оно лучше с мемоизацией или без нее.
Чтобы ответить на ваш вопрос, я не думаю, что существует явное правило, когда вы должны или не должны мемоизировать компоненты, однако я думаю, что следует применять тот же принцип, что и при решении, следует ли вам переопределять shouldComponentUpdate(): найти проблемы с производительностью с помощью предлагаемого инструменты профилирования и определить, нужно ли вам оптимизировать компонент.
Это отличный ответ, и я думаю, вы абсолютно правы в том, что профилирование - необходимый инструмент для определения масштабов ошибок производительности.
Да, я думаю, что многие функции, которые дает нам React, не предназначены для того, чтобы быть волшебными палочками, это больше инструментов, помогающих решать проблемы в определенных (иногда общих) ситуациях, я рад, что вы нашли мой ответ полезным @KeithBrewster
Вы должны были упомянуть о последствиях для памяти (память - это не производительность). Я не знаю, почему люди думают, что стоимость React.memo - это просто дополнительное поверхностное сравнение. Когда на самом деле это «дополнительное поверхностное сравнение» + «дополнительный снимок рендеринга для каждого экземпляра компонента». По крайней мере, для функциональных компонентов.
Я думаю, вы упомянули, что shouldComponentUpdate () выполняет поверхностное сравнение состояния и свойств. Я думаю, что сравнение касается только реквизита, а не состояния. Сравнение состояния выполняется еще до вызова этого метода, потому что, если состояние такое же, вы не получите никакого обратного вызова в методе shouldComponentUpdate (). @NtokozoZwane
@IvanKleshnin интересный момент! Мне было бы интересно проработать второй оператор «дополнительный снимок рендеринга для каждого экземпляра компонента».
@NickMitchell Я имел в виду, что внутреннее устройство React.memo хранит значения свойств в памяти (чтобы иметь возможность сравнивать с новыми значениями свойств). Если эти данные достаточно велики, они могут иметь ощутимый след.
Поправьте меня, если я ошибаюсь или неправильно понимаю, но я считаю, что React.Component не реализует shouldComponentUpdate (). Скорее, только React.PureComponent реализует shouldComponentUpdate (). Источник: reactjs.org/docs/react-api.html#reactpurecomponent.
Тот же вопрос имеет ответ от markerikson в системе отслеживания проблем React GitHub. Он получил больше одобрения, чем ответов здесь.
I would assume that the same general advice applies for
React.memoas it does forshouldComponentUpdateandPureComponent: doing comparisons does have a small cost, and there's scenarios where a component would never memoize properly (especially if it makes use ofprops.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: В этом случае 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-компонента быть дешевле, чем буквальное сравнение значений, что, возможно, является одной из самых быстрых операций во всех языках программирования? это не правильно на многих уровнях. Хотя это правда, что бездумное запоминание компонентов может буквально занять больше циклов процессора, но это никак не сравнится с каким-либо повторным рендерингом, если вы не сравните буквально тысячи параметров одновременно, что маловероятно.
Что ж, моя основная мысль: учитывая, что ваш компонент в большинстве случаев все равно изменил реквизиты в случае родительского повторного рендеринга, React.memo должен выполнять больше работы, чем простой компонент (согласование различий в свойствах а также). Я лично также не являюсь поклонником преждевременная оптимизация - эти изменения всегда имеют свою стоимость (например, стоимость добавленной абстракции / сложности) и должны быть оправданы.
Также интересно: в связанной статье худшая производительность был испытан и измерен после установки PureComponent aka React.memo для функций на каждом компоненте.
Вы правы в большинстве своих пунктов, и я согласен со всем, кроме дешевизны повторного рендеринга. очевидно, что для компонента, который ВСЕГДА повторно обрабатывается, запоминание бесполезно и плохо сказывается на производительности. Я просто считаю, что самое прекрасное в фреймворках React и React-kind - это идея виртуального дома, основная цель которого - предотвратить ненужные дорогостоящие манипуляции с реальным DOM. Так что, помимо прочего, это своего рода суть фреймворка. поверхностное сравнение подходит не для каждого сценария, но все же является хорошей идеей для многих сценариев.
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 думает о React.memo как о useEffect (Component, [в зависимости ...]) Только в случае useEffect в документации изначально говорилось, что значения массива сравниваются поверхностно =), а в случае props это не было заявлено, это причина, по которой React.memo не включен по умолчанию =) Кстати, я являюсь одним из разработчиков реакции.
@anonym У нас была идея, что React.memo не остановит рендеринг своих дочерних элементов, но дети также будут проверяться на предмет изменений свойств. Теперь React.memo блокирует рендеринг всей ветки своего дерева, это также одна из причин, по которой он не включен по умолчанию. Уловка в том, что у реактивности есть такое понятие, как «зерно реактивности»; у каждого зерна есть зависимости. Зависимости сравниваются, и если они не изменились, зернистость не пересчитывается. Это стандартные оптимизации FRP (React - одна из реализаций FRP)
@anonym Хотя я не смотрел код реакции с момента добавления волокон, мне нужно посмотреть, блокирует ли memo повторную проверку зависимостей дочерних компонентов (реквизита). Но суть не сильно меняется. Реактивность - это «императивный декларативный» =), это фрагменты императивного кода, которые повторно выполняются при изменении их зависимостей. отсюда и слово activation и REactivation (когда зависимости изменились) =), в общем, React.memo пока надо писать вручную (позже напишу плагин babel)
@anonym Я считаю, что единственное предостережение - на случай, если ваш реквизит перегружен памятью. Мемоизация означает сохранение свойств в памяти потенциально навсегда, то есть до следующего повторного рендеринга, где необходимо сохранить новые объекты, потенциально занимающие много памяти. Эта память будет освобождена только тогда, когда компонент будет размонтирован. Это компромисс между скоростью и памятью.
Этот ответ просто неверен и вводит в заблуждение. На самом деле очень легко представить случаи, когда сравнение свойств будет медленнее, чем сравнение VDOM. Помните, что дерево VDOM - это обычные данные JS. Теперь все сводится к разнице между размерами пропсов и дерева VDOM. Если ваш компонент возвращает относительно большой VDOM и принимает реквизиты с относительно небольшим размером (обычный сценарий), он получит выгоду от React.memo. Если hovewer ваш компонент возвращает относительно небольшой VDOM, а реквизиты относительно большие ... будет быстрее сравнивать VDOM и избегать лишнего сравнения реквизитов.
Кто-нибудь знает плагин babel, который автоматически обертывает все функциональные компоненты с помощью React.memo?
Если ваш компонент всегда повторно рендерится, он будет каждый раз выполнять ненужную мелкую проверку свойств. Это то же самое, что и PureComponent.