ReactJS + Material UI - отличные инструменты для быстрой разработки веб-приложений, но очень часто неконтролируемое использование приводит к множеству проблем, таких как потеря рендеринга, низкая производительность, ошибочный интерфейс и т. Д.

Итак, поговорим об оптимизации на конкретном примере.

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

Все отмеченные элементы должны храниться в родительском компоненте контейнера для дальнейшего экспорта отмеченных элементов в поток приложения.

Я не буду сосредотачиваться на материальном макете пользовательского интерфейса, React Router и других вещах, поскольку это статья о оптимизации рендеринга, вы можете проверить код приложения в репозитории github https://github.com/freshmilkdev/ реакция-оптимизация рендеринга

Базовая структура данных выглядит так:

Начнем с создания контейнерного компонента ListContainer и функции createFakeDataSource для нашей демонстрации (в реальном приложении данные были получены из Firestore, но статья не об этом):

Презентационный компонент для списка с оболочкой панели Material UI Expansion:

И дочерний компонент ListItem:

Итак, на данный момент у нас есть неоптимизированный пользовательский интерфейс с источником данных только для 10 списков, содержащих 10 подпунктов.

Давайте проверим производительность рендеринга при первом рендеринге компонента.

Как вы можете видеть в выводе консоли, у нас есть несколько плохих моментов для производительности приложения:

Проблема №1.

Так как ListItem Checkbox является контролируемым компонентом, каждый элемент ListItem в каждый список отображается каждый раз, я переключаю одно значение флажка, даже это значение не принадлежат текущему ListItem. Это тест для 10 списков и 10 подэлементов, поэтому каждый раз, когда checkItems изменяется в ListContainer, response будет повторно отображать 100 ListItems вместо одного целевого элемента. Очевидно, что рендеры потрачены впустую. Только представьте, а что, если элементов будет 1000 или даже больше?

Проблема №2.

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

Проблема №1 исправление

React предоставляет нам отличное решение для этого - PureComponent:

React.PureComponent похож на React.Component. Разница между ними в том, что React.Component не реализует shouldComponentUpdate(), а React.PureComponent реализует его с помощью поверхностного сравнения свойств и состояний.

Если функция render() вашего компонента React отображает тот же результат при тех же свойствах и состоянии, вы можете использовать React.PureComponent для повышения производительности в некоторых случаях.

Перед выпуском React v16.6.0 мы должны создать наши презентационные компоненты, расширяющие класс PureComponent, и теперь у нас есть потрясающая функция React.memo:

React.memo - это компонент более высокого порядка. Он похож на React.PureComponent, но для функциональных компонентов, а не для классов.

Итак, давайте обернем наш ListItem с помощью React.memo:

И проверьте мгновенно повышенную производительность:

Вот и все! На данный момент response будет повторно отображать только целевой ListItem, когда значение флажка изменится.

Проблема # 2 исправление

Изначально все ExpansionPanel свернуты, так почему мы должны изначально визуализировать все вложенные элементы? Возможно, наш пользователь даже не будет расширять Список №7 или Список №10.

Теперь у нас много потерянных рендеров.

Как насчет того, чтобы отображать дочерний список только, когда пользователь действительно разворачивает целевую панель?

До React v16.8 мы должны были использовать HOC для достижения этой цели, но теперь у нас есть замечательная функция, называемая React Hooks - они позволяют вам использовать состояние и другие функции React без написания класса.

Давайте управлять отображением нашего списка в зависимости от развернутого значения:

Вот некоторые объяснения внесенных нами изменений:

  1. Импортировать крючок
import React, {useState} from 'react';

2. Объявите локальное значение и обработчик:

const [expanded, setExpanded] = useState(false);

3. Вызвать обработчик, когда панель действительно развернута:

<ExpansionPanel className={classes.list} onClick={() => setExpanded(true)}>

4. Используйте условие для рендеринга только в развернутом виде:

<ExpansionPanelDetails>
    {expanded &&
    <MUIList>
        {list.items.map(item =>
            <ListItem
                checked={checkedItems[item.value] !== undefined}
                onCheckItem={onCheckItem}
                key={item.id}
                item={item}/>
        )}
    </MUIList>}
</ExpansionPanelDetails>

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

Спасибо за прочтение!

Надеюсь, этот пост был для вас полезен :)

Исходный код демонстрационного приложения на GitHub:

Https://github.com/freshmilkdev/react-render-optimization

Добро пожаловать в комментарии для любых мыслей или предложений.

Ваше здоровье!