Как быстро начать работу с VUE3.0: Начало работы
В этом уроке мы узнаем, как реализовать мемоизацию в React. Мемоизация повышает производительность за счет кэширования результатов вызовов функций и возврата этих кэшированных результатов, когда они снова потребуются.
Мы рассмотрим следующее:
В этой статье предполагается, что вы имеете базовое представление о компонентах классов и функций в React.
Если вы хотите ознакомиться с этими темами, вы можете просмотреть официальную документацию по компонентам и реквизитам React
https://reactjs.org/docs/comComponents-and-props.html.
Прежде чем обсуждать детали мемоизации в React, давайте сначала посмотрим, как React использует виртуальный DOM для рендеринга пользовательского интерфейса.
Обычный DOM в основном содержит набор узлов, представленных в форме дерева. Каждый узел в DOM представляет собой элемент пользовательского интерфейса. Всякий раз, когда в приложении происходит изменение состояния, соответствующие узлы для этого элемента пользовательского интерфейса и всех его дочерних элементов обновляются в дереве DOM, что затем запускает перерисовку пользовательского интерфейса.
С помощью эффективных алгоритмов дерева DOM обновление узлов происходит быстрее, но перерисовка происходит медленно и может повлиять на производительность, если DOM имеет большое количество элементов пользовательского интерфейса. Поэтому в React был введен виртуальный DOM.
Это виртуальное представление реального DOM. Теперь, когда происходит какое-либо изменение в состоянии приложения, React не обновляет реальный DOM напрямую, а создает новый виртуальный DOM. Затем React сравнит этот новый виртуальный DOM с ранее созданным виртуальным DOM, найдет различия (примечание переводчика: то есть найдет узлы, которые необходимо обновить), а затем перерисует.
Благодаря этим различиям виртуальный DOM может более эффективно обновлять реальный DOM. Это повышает производительность, поскольку виртуальный DOM не просто обновляет элемент пользовательского интерфейса и все его дочерние элементы, а эффективно обновляет только необходимые и минимальные изменения в реальном DOM.
В предыдущем разделе мы увидели, как React использует виртуальный DOM для эффективного выполнения операций обновления DOM и повышения производительности. В этом разделе мы представим пример, объясняющий необходимость использования мемоизации для дальнейшего повышения производительности.
Мы создадим родительский класс, содержащий кнопку, которая увеличивает переменную с именем count
. Родительский компонент также вызывает дочерний компонент и передает ему параметры. Мы также добавили оператор console.log()
в метод render
:
//Parent.js. класс Parent расширяет React.Component { конструктор (реквизит) { супер(реквизит); this.state = {count: 0}; } handleClick = () => { this.setState((prevState) => { return {count: prevState.count + 1}; }); }; оказывать() { console.log("Родительский рендеринг"); возвращаться ( <div className="Приложение"> <button onClick={this.handleClick}>Приращение</button> <h2>{this.state.count</h2> <Имя ребенка={"Джо"} /> </div> ); } } экспортировать родительский элемент по умолчанию.
Полный код этого примера можно просмотреть на CodeSandbox.
Мы создадим Child
класс, который принимает параметры, передаваемые родительским компонентом, и отображает их в пользовательском интерфейсе:
//Child.js. класс Child расширяет React.Component { оказывать() { console.log("Дочерний рендеринг"); возвращаться ( <дел> <h2>{this.props.name</h2> </div> ); } } экспортировать по умолчанию Child;
Значение count
будет меняться каждый раз, когда мы нажимаем кнопку в родительском компоненте. Поскольку состояние изменилось, выполняется метод render
родительского компонента.
Параметры, передаваемые дочернему компоненту, не изменяются каждый раз при повторной визуализации родительского компонента, поэтому дочерний компонент не должен повторно отображаться. Однако когда мы запускаем приведенный выше код и продолжаем увеличивать count
, мы получаем следующий результат:
Родительский рендеринг Детский рендеринг Родительский рендеринг Детский рендеринг Родительский рендеринг Дочерний рендеринг.
Вы можете опробовать приведенный выше пример в этой песочнице и просмотреть вывод консоли.
Из выходных данных мы видим, что при повторной визуализации родительского компонента дочерний компонент выполняет повторную визуализацию, даже если параметры, переданные дочернему компоненту, остаются неизменными. Это приведет к тому, что виртуальный DOM дочернего компонента выполнит проверку различий с предыдущим виртуальным DOM. Поскольку в наших дочерних компонентах нет никаких изменений и все реквизиты не изменяются при повторном рендеринге, реальный DOM не будет обновляться.
То, что реальный DOM не обновляется без необходимости, определенно повышает производительность, но мы видим, что создается новый виртуальный DOM и выполняется проверка различий, даже если в дочерних компонентах нет фактических изменений. Для небольших компонентов React эти затраты на производительность незначительны, но для больших компонентов влияние на производительность может быть значительным. Чтобы избежать повторного рендеринга и дифференциальной проверки виртуального DOM, мы используем мемоизацию.
В контексте приложений React мемоизация — это средство, с помощью которого всякий раз, когда родительский компонент выполняет повторную визуализацию, дочерний компонент выполняет повторную визуализацию только тогда, когда реквизиты, от которых он зависит, изменяются. Если в реквизитах, от которых зависит дочерний компонент, нет изменений, он не выполнит метод рендеринга и вернет кэшированные результаты. Поскольку метод рендеринга не выполняется, не будет создания виртуального DOM и дифференциальной проверки, что приведет к повышению производительности.
Теперь давайте посмотрим, как реализовать мемоизацию в компонентах классов и функций, чтобы избежать ненужного повторного рендеринга.
Чтобы реализовать мемоизацию в компоненте класса, мы будем использовать React.PureComponent. React.PureComponent
реализует метод mustComponentUpdate(), который выполняет поверхностное сравнение state
и props
и повторно отображает компонент React только в случае изменения реквизита или состояния.
Измените дочерний компонент на такой код:
//Child.js class Child расширяет React.PureComponent { // Здесь мы меняем React.Component на React.PureComponent оказывать() { console.log("Дочерний рендеринг"); возвращаться ( <дел> <h2>{this.props.name</h2> </div> ); } } экспортировать по умолчанию Child;
Полный код этого примера показан в этой песочнице.
Родительский компонент остается неизменным. Теперь, когда мы увеличиваем count
в родительском компоненте, вывод в консоли выглядит следующим образом:
Родительский рендеринг Детский рендеринг Родительский рендеринг Родительский рендеринг
. Для первого рендеринга он вызывает метод render
как родительского, так и дочернего компонента.
Для каждого повторного рендеринга после увеличения count
вызывается только функция render
родительского компонента. Дочерние компоненты не будут повторно отображаться.
Чтобы реализовать мемоизацию в функциональных компонентах, мы будем использовать React.memo(). React.memo()
— это компонент более высокого порядка (HOC), который выполняет работу, аналогичную PureComponent
, чтобы избежать ненужного повторного рендеринга.
Вот код функционального компонента:
//Child.js функция экспорта Child(реквизит) { console.log("Дочерний рендеринг"); возвращаться ( <дел> <h2>{props.name</h2> </div> ); } Export default React.memo(Child); // Здесь мы добавляем HOC к дочернему компоненту для реализации мемоизации
, а также преобразуем родительский компонент в функциональный компонент, как показано ниже:
//Parent.js экспортировать функцию по умолчанию Parent() { const [count, setCount] = useState(0); const handleClick = () => { setCount(количество + 1); }; console.log("Родительский рендеринг"); возвращаться ( <дел> <button onClick={handleClick}>Приращение</button> <h2>{count</h2> <Имя ребенка={"Джо"} /> </div> ); }
Полный код этого примера можно увидеть в этой песочнице.
Теперь, когда мы увеличиваем count
в родительском компоненте, на консоль будет выводиться следующее:
Родительский рендеринг Детский рендеринг Родительский рендеринг Родительский рендеринг
родительским рендерингомReact.memo()
В приведенном выше примере мы видим, что когда мы используем HOC React.memo()
для дочернего компонента, дочерний компонент не отображается повторно, даже если повторно отображается родительский компонент.
Однако следует отметить одну небольшую вещь: если мы передаем функцию в качестве параметра дочернему компоненту, дочерний компонент будет повторно отображаться даже после использования React.memo()
. Давайте посмотрим на пример этого.
Мы изменим родительский компонент, как показано ниже. Здесь мы добавляем функцию-обработчик и передаем ее в качестве параметра дочернему компоненту:
//Parent.js экспортировать функцию по умолчанию Parent() { const [count, setCount] = useState(0); const handleClick = () => { setCount(количество + 1); }; константный обработчик = () => { console.log("handler"); // Функция-обработчик здесь будет передана дочернему компоненту}; console.log("Родительский рендеринг"); возвращаться ( <div className="Приложение"> <button onClick={handleClick}>Приращение</button> <h2>{count</h2> <Child name={"Джо"} childFunc={handler} /> </div> ); }
Код дочернего компонента останется без изменений. Мы не будем использовать функции, переданные из родительских компонентов, в дочерние компоненты:
//Child.js функция экспорта Child(реквизит) { console.log("Дочерний рендеринг"); возвращаться ( <дел> <h2>{props.name</h2> </div> ); } Export default React.memo(Child);
Теперь, когда мы увеличиваем count
в родительском компоненте, он одновременно выполняет повторную визуализацию и повторную визуализацию дочернего компонента, даже если в переданных параметрах нет изменений.
Итак, что же вызывает повторный рендеринг подкомпонента? Ответ заключается в том, что каждый раз, когда родительский компонент выполняет повторную визуализацию, создается новая функция- handler
, которая передается дочернему компоненту. Теперь, поскольку функция handle
воссоздается при каждом повторном рендеринге, дочерний компонент обнаружит, что ссылка handler
изменилась при поверхностном сравнении реквизитов, и повторно отрисует дочерний компонент.
Далее мы расскажем, как решить эту проблему.
useCallback()
для предотвращения повторной визуализацииОсновная проблема с повторной визуализацией дочернего компонента заключается в том, что функция- handler
создается заново, что изменяет ссылку, передаваемую дочернему компоненту. Поэтому нам нужен способ избежать этого дублирования. Если функция- handler
не создается заново, ссылка на функцию- handler
не изменяется, поэтому дочерний компонент не выполняет повторную визуализацию.
Чтобы избежать необходимости воссоздавать функцию каждый раз при рендеринге родительского компонента, мы будем использовать React Hook под названием useCallback(). Хуки были представлены в React 16. Чтобы узнать больше о хуках, вы можете ознакомиться с официальной документацией по хукам React или ознакомиться с разделом «React Hooks: Как начать работу и создать свои собственные».
Хук useCallback()
принимает два параметра: функцию обратного вызова и список зависимостей. Ниже
приведен пример useCallback()
:
const handleClick = useCallback(() => {. //Сделать что-нибудь }, [x,y]);
Здесь к функции handleClick()
добавляется useCallback()
. Второй параметр [x, y]
может быть пустым массивом, одной зависимостью или списком зависимостей. Функция handleClick()
воссоздается только при изменении какой-либо из зависимостей, упомянутых во втором параметре.
Если зависимости, упомянутые в useCallback()
не изменились, то возвращается мемоизированная версия функции обратного вызова, указанная в качестве первого аргумента. Мы изменим родительский компонент, чтобы использовать хук useCallback()
для обработчиков, передаваемых дочернему компоненту:
//Parent.js экспортировать функцию по умолчанию Parent() { const [count, setCount] = useState(0); const handleClick = () => { setCount(количество + 1); }; const handler = useCallback(() => { // Используйте useCallback() для функции-обработчика console.log("обработчик"); }, []); console.log("Родительский рендеринг"); возвращаться ( <div className="Приложение"> <button onClick={handleClick}>Приращение</button> <h2>{count</h2> <Child name={"Джо"} childFunc={handler} /> </div> ); }
Код дочернего компонента останется без изменений.
Полный код этого примера находится в этой песочнице.
Когда мы увеличиваем count
в родительском компоненте приведенного выше кода, мы видим следующий результат:
Родительский рендеринг Детский рендеринг Родительский рендеринг Родительский рендеринг Родительский рендеринг
Поскольку мы используем перехватчик useCallback()
для handler
в родительском компоненте, функция handler
не будет воссоздаваться каждый раз при повторной визуализации родительского компонента, а версия handler
Memoization будет передаваться дочернему компоненту. Дочерний компонент выполнит поверхностное сравнение и заметит, что ссылка на функцию handler
не изменилась, поэтому он не будет вызывать метод render
.
мемоизация — хорошее средство избежать ненужного повторного рендеринга компонентов, когда их состояние или свойства не изменились, тем самым улучшая производительность приложений React. Вы можете рассмотреть возможность добавления Memoization ко всем вашим компонентам, но это не обязательно способ создания высокопроизводительных компонентов React. Мемоизацию следует использовать только в том случае, если компонент:
В этом уроке мы поняли:
React.memo()
для функциональных компонентов и React.PureComponent
для компонентов класса.React.memo()
useCallback()
избежать проблем с повторным рендерингом, когда функции передаются в качестве реквизитов дочерним компонентам.Надеюсь, это введение в React Memoization будет вам полезно!
Исходный адрес: https://www.sitepoint.com/implement-memoization-in-react-to-improve- Performance/Первоначальный
автор: Нида Хан