В этом блоге объясняются основы процесса рендеринга React, как это влияет на производительность компонентов, а также решения для предотвращения ненужного повторного рендеринга компонентов. Он подробно объясняет один из методов оптимизации производительности в компонентах: функцию memo.

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

React рендерит пользовательский интерфейс с помощью функции render( ), думайте об этом как о создании дерева реагирующих элементов, которые мы хотим показать в браузере. React сохраняет это дерево перед обновлением DOM браузера элементами пользовательского интерфейса из этого дерева. Команда React называет это сохраненное дерево Virtual DOM. Это внутреннее представление отображаемого пользовательского интерфейса. Это копия фактического DOM браузера.

Согласование — еще один модный термин, который использует команда React. Проще говоря, это процесс обновления DOM браузера последними изменениями, происходящими из изменений состояния в реагирующем приложении. И Virtual DOM является важной частью этого процесса. Когда состояние или свойства компонента изменяются, функция render( ) создает новое дерево элементов. React сравнивает это новое дерево с Virtual DOM, используя алгоритм "отличания", и решает, необходимо ли фактическое обновление DOM.

React начинает сравнивать деревья с корня. Всякий раз, когда корневые элементы вновь созданного дерева и Virtual DOM имеют разные типы, React разрушит старое дерево и построит новое дерево с нуля. Например, переход от <p> к <span> или от <Cats> к <Dogs> приведет к полной реконструкции дерева. , тем самым уничтожая старые узлы DOM. Когда два элемента имеют одинаковый тип, React сравнивает атрибуты обоих и обновляет только измененные атрибуты.

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

React предоставляет нам инструменты, которые помогают нам в этом отношении:

1. memo()похож на PureComponent в случае компонентов класса.

2. useMemo()

3. useCallback()

В этом сообщении блога мы будем обсуждать только memo функцию.

Функция запоминания:

Это функция, которая используется для создания чистых компонентов (чистый компонент — это компонент, который всегда выводит один и тот же результат с одинаковыми параметрами/свойствами)

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

const Cat = ( { name } )  =>  {
       console.log(`fresh render - ${name}`);
       return <p>{name}</p>
}
const App = () => {
       const [cats, setCats] = useState(['Cute', 'Sweet', 'Awesome']);
       const handleAddCat = ( ) => {
            setCats([...cats, prompt("Name a cat")]);
        }
       return (
          <div>
             {cats.map((name, index) => (
                  <Cat key={index} name={name}/>
             ))}
             <button onClick={handleAddCat}>Add a Cat</button>
          </div>
      )
}

Мы создали компонент Catчистый компонент — поскольку его выходные данные не меняются при том же имени свойства.

Компонент приложения использует этот компонент Cat. После начального рендера консоль читает:

fresh render – Cute
fresh render – Sweet
fresh render – Awesome

При нажатии кнопки «Добавить кошку» пользователю предлагается добавить кошку. Допустим, мы назовем нового кота «Веселый». Как только массив котов обновляется, мы видим, что все остальные компоненты кота, которые уже были отрисованы, отрисовываются повторно, хотя их свойства (то есть имя) не изменились.

fresh render – Cute
fresh render – Sweet
fresh render – Awesome
fresh render – Fun

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

Здесь нам на помощь приходит функция запоминания. Мы обернем наш чистый компонент memo function при экспорте следующим образом:

import { memo } from "react";
const Cat = ( { name } ) => {
       console.log(`fresh render - ${name}`);
       return <p>{name}</p>
}

const PureCat = memo(Cat);
const App = () => {
        const [cats, setCats] = useState(['Cute', 'Sweet', 'Awesome']);
        const handleAddCat = ( ) =>  {
                  setCats([...cats, prompt("Name a cat")]);
         }
        return (
                <div>
                        {cats.map((name, index) => (
                            <PureCat key={index} name={name}/>
                        ))}
                       <button onClick={handleAddCat}>Add a Cat</button>
                </div>
       )
}

Мы создали новый компонент под названием PureCat. Этот компонент заставит Cat отображаться только при изменении свойств. Теперь, когда мы добавляем нового кота, в консоли отображается только он.

fresh render – Fun

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

Давайте отправим функцию мяуканье, которая утешает имя кошки, которая мяукала (на которую нажали).

import { memo } from "react";
const Cat = ( { name, meow } ) => {
      console.log(`fresh render - ${name}`);
      return <p onClick={() => meow(name)}>{name}</p>
}
const PureCat = memo( Cat );
const App = ( ) => {
   const [cats, setCats] = useState(['Cute', 'Sweet', 'Awesome']);
   const handleAddCat = () => {
        setCats([...cats, prompt("Name a cat")]);
   }
   return (
         <div>
              {cats.map((name, index) => (
                 <PureCat key={index} name={name} meow={name => console.log(`${name} has meowed`)}/>
              ))}
              <button onClick={handleAddCat}>Add a Cat</button>
         </div>
    )
}

Когда это изменение добавлено, наш PureCat ведет себя не так, как должен. Теперь, опять же, каждый кот перерисовывается, когда мы добавляем нового кота, даже если свойство имени для них не изменилось.

Это происходит потому, что когда массив кошек (состояние) изменяется в компоненте приложения, этот компонент перерисовывается и, таким образом, переопределяет PureCat. К сожалению, каждый раз, когда свойство moew переопределяется, это всегда новая функция. Чтобы отреагировать, свойство moew было изменено, и компонент перерисовывается. И это ужасное консольное сообщение о свежем рендеринге появляется у всех.

fresh render – Cute
fresh render – Sweet
fresh render – Awesome
fresh render – Fun

Теперь давайте сменим это чувство страха на спокойствие, потому что команда React предоставила нам решение. Они не стали бы нас так сушить.

Функция памятки позволяет нам определить более точные правила, когда компонент должен повторно отображаться. Функция памяти принимает второй аргумент как функцию. Эта функция решает, отображать компонент или нет. Если эта функция возвращает true, то компонент не перерисовывается, а когда она возвращает false, как вы уже догадались, компонент перерисовывается. Эта функция также получает два аргумента: предыдущие свойства и следующие свойства. Который мы можем использовать, чтобы принять решение о повторном рендеринге компонента.

А в строке 5 вы можете видеть, что мы сравниваем prevProps.name с nextProps.name, и если они равны, компонент не перерисовывается.

Окончательный код:

import { memo } from "react";
const Cat = ( { name, meow } ) => {
    console.log(`fresh render - ${name}`);
    return <p onClick={() => meow(name)}>{name}</p>
}
const PureCat = memo(Cat, (prevProps, nextPros) => prevProps.name === nextPros.name);
const App = ( ) => {
     const [cats, setCats] = useState(['Cute', 'Sweet', 'Awesome']);
     const handleAddCat = () => {
          setCats([...cats, prompt("Name a cat")]);
      }
      return (
         <div>
              {cats.map((name, index) => (
                  <PureCat key={index} name={name} meow={name => console.log(`${name} has meowed`)}/>
              ))}
              <button onClick={handleAddCat}>Add a Cat</button>
         </div>
      )
}