Вступление

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

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

С точки зрения производительности мы хотим убедиться:

  • Выполнение render метода компонента должно быть максимально быстрым, т. Е. Мы хотим избежать дорогостоящих вычислений или выделения объектов.
  • Количество вызовов метода render должно быть как можно меньшим. Каждый раз, когда вызывается render, React должен запустить свой алгоритм согласования, чтобы сравнить виртуальные DOM обновления с существующим состоянием. Хотя это реализовано очень эффективно, еще эффективнее вообще избежать примирения.

Минимизация времени рендеринга

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

Мы используем операторы RxJS, чтобы реагировать на изменения входных данных, и используем концепцию state для передачи результатов наших вычислений и создания объектов методу render.

Как избежать примирения

Разработчик компонента несет ответственность за определение того, приводит ли изменение свойства или состояния к повторной отрисовке приложения. Обычно это делается путем переопределения метода shouldComponentUpdate или путем наследования от PureComponent для простых случаев использования.

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

  • Отрисовка компонента может зависеть не напрямую от свойства, а от производной информации этого свойства. И это может оставаться стабильным, несмотря на изменение собственности.
  • Для контролируемых компонентов мы часто передаем функции обратного вызова через свойства. Иногда мы можем наблюдать (анти) паттерн, согласно которому компоненты хоста связывают функции-члены с обратными вызовами внутри своих render вызовов или что они используют лямбда-функции, сгенерированные во время рендеринга. Это будет создавать новые функциональные объекты каждый раз, когда хост выполняет рендеринг, вызывая ненужный повторный рендеринг дочернего компонента.

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

Это информация, которую полностью контролирует разработчик компонента. Мы также указываем, что объекты, переносимые в компоненте state, являются неизменяемыми, поэтому с помощью простой проверки на равенство мы можем определить, изменилось ли состояние или нет.

Разделение проблем

Обсуждаемые шаблоны оптимизации вращаются вокруг идеи вычисления идеального state для рендеринга компонента.

Это дает нам возможность разделить задачу вычисления этого состояния на Компонент бизнес-логики (BLoC) и фактический рендеринг на компонент представления.

Пример

Прежде чем мы начнем объяснять подход, давайте добавим очень простое "Hello, World!" пример.

Мы будем использовать реализацию обсуждаемого паттерна rx-react-component:

Объяснение:

  1. Создание компонента с HelloWorldProps в качестве входных данных. Компонент реализует некоторую простую бизнес-логику (префикс ввода с 'Hello'), а затем передает результат компоненту презентации.
  2. Уровень бизнес-логики, преобразующий входные свойства в состояние. Обратите внимание, как оператор independentUntilChanged гарантирует обновление состояния только в том случае, если ввод действительно изменился.
  3. Компонент презентации реализован как функциональный компонент.

Подход

Мы реализуем анонимный класс для нашего реактивного компонента, оптимизированного для производительности. Цель этого класса:

  • Предоставьте реактивный способ RxJS для вычисления state из свойств, включая реактивный доступ к методам жизненного цикла
  • Минимизируйте согласование, реализовав метод shouldComponentUpdate

Минимизация времени рендеринга

Мы представляем Методы жизненного цикла React как Наблюдаемые и выводим состояние компонента с помощью реактивных операторов.

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

Вызывающий создает state$ Observable на основе входных свойств (через props$ Observable) или с помощью механизмов RxJS для асинхронного вычисления состояния.

Начальное состояние

Любое состояние, которое генерируется объектом state$ Observable до вызова метода componentDidMount, автоматически считается состоянием инициализации. Вы можете использовать оператор startWith, чтобы убедиться, что такое состояние существует. Нет необходимости (и нет возможности) явно устанавливать this.state.

Вход от хост-компонента

Наш компонент React будет получать входные данные через свойства от своего хоста. Эти свойства доступны через props$ Observable.

Для доступа к отдельным свойствам используйте такие операторы, как pluck и independentUntilChanged, и изменяйте состояние, только если эти свойства изменяются.

Входные данные из дочерних компонентов

Связь дочернего компонента с родительским обычно осуществляется путем передачи функции обратного вызова в качестве обработчика события через свойство дочернему компоненту.

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

Поскольку мы разделяем наш компонент на BLoC и компонент презентации, компонент презентации всегда должен управляться BLoC, тогда как BLoC может управляться или не контролироваться.

Управление компонентом представления: мы определяем функции обратного вызова для изменений состояния компонента представления и управляем ими в состоянии BLoC. Эти функции связаны next вызовами Subject, что позволяет BLoC интегрировать эти обратные вызовы в наблюдаемый канал.

Неконтролируемый BLoC: неконтролируемый BLoC обычно поддерживает свое состояние с помощью оператора сканирования.

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

Контролируемый BLoC: управляемый BLoC делегирует управление состоянием своему хосту с помощью функции обратного вызова в своих свойствах.

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

Как избежать примирения

Абстрактный класс реализует shouldComponentUpdate и сравнивает новое состояние с текущим, используя простую equals проверку. Свойства полностью игнорируются. Это работает, потому что

  • Объекты неизменны
  • Вся информация, полученная из свойств, должна быть преобразована в state с помощью наблюдаемого state$

Разделение проблем

Мы используем функцию rxComponent для создания нашего компонента. Эта функция принимает функцию для вычисления state$ наблюдаемого из свойств, наблюдаемых жизненного цикла и ссылки на компонент представления, который принимает состояние в качестве своих входных свойств.

Такой подход имеет следующие преимущества:

  • Четко отделяет бизнес-логику от логики рендеринга
  • Нет необходимости создавать собственный класс для каждого компонента, что уменьшает общий размер приложения.

Резюме

  • Разделите свой компонент на компонент бизнес-логики (BLoC) и компонент презентации.
  • Реализуйте BLoC с помощью RxJS для преобразования свойств и контекста в состояние
  • Используйте встроенные операторы RxJS для инициализации (startWith), для минимизации обновлений (distinctUntilChanged), для поддержания состояния (scan) и для объединения с контекстными, потенциально асинхронными данными (merge, switch и т. Д.).
  • Передайте полученное состояние в качестве входных свойств компоненту презентации.

Ресурсы