Серверный рендеринг в React.js обзор свойств, создание счетчика и списка данных


Содержание материала:

Серверный рендеринг в React.js: обзор свойств, создание счетчика и списка данных

Мельчайшими блоками приложения React являются элементы. Определение простейшего элемента:

По сути элементы в React — это обычные объекты JavaScript, с которыми быстрее работать, чем с обычными элементами на веб-странице.

Для рендеринга элементов в React применяется метод ReactDOM.render() . Этот метод принимает три параметра:

ReactElement : элемент для рендеринга

DOMElement : элемент DOM, в который надо добавить ReactElement

Callback : опциональная функция обратного вызова

ReactElement может иметь только один корневой элемент, в который уже вкладываются все остальные элементы. Например, создадим новый проект. Определим в проекте следующий файл index.html :

В данном случае корневым элементом является

При этом в один элемент DOM можно добавить только один элемент ReactElement, например, добавим еще один элемент на веб-страницу:

Обновление элементов

Элементы React являются неизменяемыми (immutable). После создания элемента нельзя изменить его атрибуты или дочерние элементы. И единственный способ изменить интерфейс, определенный в элементе, это создать новый элемент и передать его в функцию ReactDOM.render(). Например:

В данном случае в элементе с >

При обновлении элемента виртуальный React DOM сравнивает текущее состояние элемента с его предыдущим состоянием и при наличии изменений применяет их к веб-странице и обновляет стандартный DOM. Но обновляться будет не весь пересоздаваемый элемент, а только текстовый узел, который содержит вывод текущего времени, так как только этот узел будет отличаться от предыдущего состояния.

Информация по асинхронной отрисовке компонентов

27 Марта, 2020. Brian Vaughn (Брайан Вон)

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

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

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

Эти методы жизненного цикла часто понимались неправильно и злоупотреблялись. Более того, мы ожидаем, что их потенциальное злоупотребление может принести еще больше проблем вместе с асинхронной отрисовкой. Из-за этого мы добавим префикс «UNSAFE_» к данным методам в предстоящей версии. (Здесь префикс «UNSAFE_» относится не к безопасности. Он сообщает, что код, использующий данные методы, будет с большей вероятностью иметь ошибки в будущих версиях React, особенно после активации асинхронной отрисовки.)

Путь постепенной миграции

React следует схеме управления версиями, поэтому данное изменение будет постепенным. Наш текущий план:

  • Релиз 16.3: Вводит псевдонимы/алиасы для небезопасных методов жизненного цикла, UNSAFE_componentWillMount , UNSAFE_componentWillReceiveProps и UNSAFE_componentWillUpdate . (В данном релизе будут работать и старые имена методов жизненного цикла, и новые псевдонимы.)
  • Будущие релизы 16.x: Будут включать предупреждение об устаревании методов componentWillMount , componentWillReceiveProps и componentWillUpdate . (В данных релизах будут работать и старые имена методов жизненного цикла, и новые псевдонимы, но старые имена будут выводить предупреждение в режиме разработки.)
  • Релиз 17.0: Удалит методы componentWillMount , componentWillReceiveProps и componentWillUpdate . (С того момента будут работать только новые UNSAFE_ имена методов жизненного цикла).

Обратите внимание: если вы являетесь разработчиком приложения React, вам не нужно ничего делать в отношении устаревших методов. Главная цель предстоящего релиза версии 16.3 заключается в том, чтобы позволить разработчикам проектов с открытым исходным кодом обновлять свои библиотеки до возникновения любых предупреждений об устаревании. Данные предупреждения не будут активированы вплоть до следующего выпуска 16.x.

Мы поддерживаем более 50 000 компонентов React в Facebook, и мы не планируем сразу их всех переписывать. Мы понимаем, что миграция требует времени. Мы будем проходить постепенный путь миграции вместе со всеми в сообществе React.

Миграция с устаревших методов жизненного цикла

Если вы хотите начать использовать новый API компонентов, представленный в React 16.3 (или если вы являетесь разработчиком, который хочет обновить свою библиотеку заранее), вот несколько примеров, которые, как мы надеемся, помогут вам посмотреть на компоненты под другим углом. Со временем мы планируем добавить дополнительные «рецепты» к нашей документации, которые покажут, как можно выполнять общие задачи таким образом, чтобы можно было избежать использования проблемных методов жизненного цикла.

Прежде чем мы начнем, кратко рассмотрим изменения жизненного цикла, запланированные для версии 16.3:

  • Мы добавляем следующие псевдонимы методов жизненного цикла: UNSAFE_componentWillMount , UNSAFE_componentWillReceiveProps и UNSAFE_componentWillUpdate . (Будут поддерживаться как старые имена методов жизненного цикла, таки и новые псевдонимы.)
  • Мы представляем два новых метода жизненного цикла: статические getDerivedStateFromProps и getSnapshotBeforeUpdate .

Новый метод жизненного цикла: getDerivedStateFromProps

Новый статический метод жизненного цикла getDerivedStateFromProps запускается после создания экземпляра компонента, перед его повторной отрисовкой. Он может вернуть объект для обновления состояния state или null , чтобы указать, что новые свойства props не требуют каких-либо обновлений состояния state .

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

Как устаревший componentWillReceiveProps , так и новый getDerivedStateFromProps методы придают значительную сложность компонентам. Это часто приводит к ошибкам. Рассмотрим более простые альтернативы производному состоянию, чтобы сделать компоненты предсказуемыми и поддерживаемыми.

Новый метод жизненного цикла: getSnapshotBeforeUpdate

Новый метод жизненного цикла getSnapshotBeforeUpdate вызывается непосредственно перед произведением мутаций (например, перед обновлением DOM). Возвращаемое значение для данного метода жизненного цикла будет передано в качестве третьего параметра в componentDidUpdate . (Данный метод жизненного цикла нужен не так часто, но может быть полезен в таких случаях, как ручное сохранение положения прокрутки во время перерисовок).

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

Далее мы рассмотрим примеры использования данных методов ЖЦ.

Примеры

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

Инициализация состояния

В данном примере показан компонент с вызовом setState внутри componentWillMount :

Простейшим рефакторингом для такого случая является перенос инициализации состояния в конструктор или инициализатор свойств, например:

Получение внешних данных

Ниже приведен пример компонента, который использует componentWillMount для извлечения внешних данных:

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

Рекомендуемый путь апгрейда для большинства ситуаций — это перенос логики извлечения данных в componentDidMount :

Существует распространенное заблуждение, что логика извлечения данных в componentWillMount позволяет избежать отображения пустого состояния при первой отрисовке. На практике это никогда не соответствовало действительности, потому что React всегда выполнял отрисовку сразу после componentWillMount . Если данные не доступны к моменту срабатывания componentWillMount , первая отрисовка будет по-прежнему показывать состояние загрузки независимо от того, где вы инициируете извлечение данных. Вот почему перенос fetch в componentDidMount не дает ощутимой разницы в подавляющем большинстве случаев.

В некоторых продвинутых случаях использования (например, библиотеки, такие как Relay), возможно, захочется поэкспериментировать с предварительной асинхронной выборкой данных. Пример того, как это можно сделать, доступен здесь.

В более долгосрочной перспективе канонический способ получения данных в компонентах React, скорее всего, будет основан на API-интерфейсе «приостановка», представленном на JSConf Iceland. Как решения по простому извлечению данных, так и библиотеки, такие как Apollo или Relay, смогут использовать его у себя под капотом. Он наименее многословен, чем любое из вышеперечисленных решений, но, к сожалению, не будет завершен к моменту выпуска 16.3.

В настоящее время при поддержке отрисовки на сервере необходимо предоставлять данные синхронно — ранее для этой цели часто использовался componentWillMount , но в качестве замены можно использовать и конструктор. Предстоящий API-интерфейс приостановки сделает асинхронную выборку данных возможной (и в аккуратной форме) как для серверной, так и для клиентской отрисовок.

Добавление слушателей событий (или подписок)

Ниже приведен пример компонента, который подписывается на диспетчер внешних событий при монтировании:

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

Люди часто предполагают, что componentWillMount и componentWillUnmount всегда сопряжены, но это не гарантируется. Только единожды, после того как был вызван componentDidMount , React гарантирует, что componentWillUnmount будет вызван позже для очистки.

По этой причине рекомендуемым способом добавления слушателей/подписок является использование метода жизненного цикла componentDidMount :

Иногда важно обновлять подписки в ответ на изменения свойств. Если вы используете библиотеку, такую как Redux или MobX, компонент-контейнер библиотеки должен обрабатывать это за вас. Для разработчиков приложений мы создали небольшую библиотеку, create-subscription, чтобы помочь с этим. Мы опубликуем ее вместе с React 16.3.

Библиотекам, таким как Relay/Apollo, следует вручную управлять подписками с помощью тех же методов, что и create-subscription использует у себя под капотом (как показано здесь), а также таким образом, который наиболее оптимален для использования в данной библиотеке.

Обновление состояния state на основе свойств props

Как старый componentWillReceiveProps , так и новый getDerivedStateFromProps методы добавляют значительную долю сложности компонентам. Это часто приводит к ошибкам. Рассмотрите более простые альтернативы производному состоянию, чтобы сделать компоненты предсказуемыми и поддерживаемыми.

Ниже приведен пример компонента, который использует устаревший метод ЖЦ componentWillReceiveProps для обновления состояния на основе новых значений свойств props:

Хотя вышеупомянутый код не содержит проблем, метод ЖЦ componentWillReceiveProps часто используется неправильно, что создает разные проблемы. По этой причине командой принято решение сделать его устаревшим.

Начиная с версии 16.3, рекомендуемый способ обновления состояния в ответ на изменения свойств props связан с новым статическим методом ЖЦ getDerivedStateFromProps . (Данный метод вызывается, после того как компонент был создан и каждый раз, когда он получает новые свойства):

В приведенном выше примере вы можете заметить, что props.currentRow отражен в состоянии (как state.lastRow ). Это позволяет getDerivedStateFromProps получить доступ к предыдущему значению свойства таким же образом, как это сделано в componentWillReceiveProps .

Вы могли задаться вопросом, почему мы просто не передаем предыдущие свойства в качестве параметра getDerivedStateFromProps . Мы рассматривали такой вариант при разработке API, но в конечном итоге выступили против него по двум причинам:

  • Параметр prevProps был бы нулевым при первом вызове getDerivedStateFromProps (после создания экземпляра), что требует добавление проверки if-not-null для доступа к prevProps в любой момент времени.
  • Не передавать предыдущие свойства этой функции — это шаг к освобождению памяти в будущих версиях React. (Если React не нужно передавать предыдущие свойства методам ЖЦ, то тогда ему не нужно хранить предыдущий объект props в памяти.)

Если вы пишете компонент для общего использования, полифил react-lifecycles-compat позволяет использовать новый метод ЖЦ getDerivedStateFromProps в старых версиях React. Подробнее о том, как его использовать будет показано ниже.

Вызов внешних коллбэков

Ниже приведен пример компонента, который вызывает внешнюю функцию при изменении своего внутреннего состояния:

Иногда люди используют componentWillUpdate из-за неуместного опасения, что якобы к моменту срабатывания componentDidUpdate , будет «слишком поздно» обновлять состояние других компонентов. Но это не тот случай. React гарантирует, что любые вызовы setState , которые происходят внутри componentDidMount и componentDidUpdate , будут произведены до того, как пользователь увидит обновленный интерфейс. В общем, лучше избегать таких каскадных обновлений, как это, хотя в некоторых случаях они бывают необходимы (например, если вам нужно спозиционировать всплывающую подсказку после измерения отображённого DOM элемента).

Тем не менее, небезопасно использовать componentWillUpdate для этой цели в асинхронном режиме, поскольку внешний коллбэк для одного обновления может быть вызван несколько раз. Вместо него должен использоваться метод ЖЦ componentDidUpdate , так как он гарантированно будет вызываться только один раз для одного обновления:

Побочные эффекты при изменении свойств props

Как и в примере выше, иногда у компонентов есть побочные эффекты, когда свойства props изменяется.

Как и componentWillUpdate , метод componentWillReceiveProps может вызываться несколько раз для одного обновления. По этой причине важно избегать появления побочных эффектов в данном методе. Вместо него должен использоваться метод componentDidUpdate , поскольку он гарантированно будет вызываться только один раз за одно обновление:

Получение внешних данных при изменении свойств props

Ниже приведен пример компонента, который извлекает внешние данные на основе значений из props :

Рекомендуемый путь апгрейда для этого компонента — перенос обновлений данных в componentDidUpdate . Вы также можете использовать новый метод ЖЦ getDerivedStateFromProps для очистки устаревших данных перед отрисовкой новых свойств:

Если вы используете HTTP-библиотеку, которая поддерживает отмену, например, axios, то при демонтировании очень просто отменить запрос, находящийся в состоянии выполнения. При использовании нативных Promise вы можете использовать подход, подобный показанному здесь.

Чтение DOM свойств перед обновлением

Ниже приведен пример компонента, который считывает свойство из DOM перед обновлением, для поддержки положения прокрутки в списке:

В приведенном выше примере componentWillUpdate используется для чтения свойства DOM. Однако при асинхронной отрисовке могут возникать задержки между методами ЖЦ фазы отрисовки (render-фазы) (например, componentWillUpdate и render ) и методами ЖЦ фазы фиксации (commit-фазы) (например, componentDidUpdate ). Если в это время пользователь производит действия, наподобие изменения размера окна, значение scrollHeight , считанное в componentWillUpdate , будет устаревшим.

Два метода ЖЦ могут использоваться вместе следующим образом:

Если вы пишете компонент для общего использования, полифил react-lifecycles-compat позволяет использовать новый метод ЖЦ getSnapshotBeforeUpdate в старых версиях React. Подробнее о том, как его использовать будет показано ниже.

Другие сценарии

Несмотря на то, что в этом статье мы пытались охватить наиболее распространенные случаи использования, мы признаем, что все же могли что-то пропустить. Если вы используете componentWillMount , componentWillUpdate или componentWillReceiveProps какими-либо способами, которые не охвачены этой статьей, и не уверены, как правильно мигрировать с этих методов ЖЦ, пожалуйста, создайте новую проблему рядом с нашей документацией, предоставив примеры кода и как можно попутной информации. Мы обновим данную статью новыми альтернативными паттернами по мере их появления.

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

Разработчики проектов с открытым исходным кодом могут задаваться вопросом: что означают эти изменения для компонентов общего пользования? Если вы реализуете приведенные выше предложения, что произойдет с компонентами, которые зависят от нового статического метода ЖЦ getDerivedStateFromProps ? Вам тоже придётся выпустить новую major-версию проекта и отказаться от совместимости с React 16.2 и старше?

Когда будет опубликован React 16.3, мы также опубликуем и новый пакет npm, react-lifecycles-compat. Данный пакет будет производить полизаполнение компонентов таким образом, что новые методы ЖЦ getDerivedStateFromProps и getSnapshotBeforeUpdate также будут работать и с более старыми версиями React (0.14.9+).

Чтобы использовать этот полифил, сначала добавьте его как зависимость в вашу библиотеку:

Затем обновите свои компоненты, чтобы использовать новые методы ЖЦ (как описано выше).

Наконец, используйте полифил для того, чтобы ваш компонент был обратно совместим со старыми версиями React:

Серверный рендеринг в React и ошибки при использовании EJS как вид двигатель

Сначала я создал мое приложение без какого-либо вида двигателя. Мой сервер выглядит следующим образом:

И моя функция toHtml выглядела следующим образом (конечно, до компиляции):

Все работает просто отлично, приложение было оказание хорошо, и я был в состоянии маршрута между различными компонентами с помощью ссылки. Проблема заключалась в том, что я не мог перезагрузить страницу по указанному маршруту. Например, я мог ударить обновления на локальном хосте: 3000, но после обновления на локальном хосте: 3000 / о я получаю сообщение об ошибке «двигатель по умолчанию не был указан и не было предоставлено никакого расширения.» Так что я решил использовать EJS как мой вид двигатель.

После chenges Обязательной мое приложение выглядит так:

и мои index.ejs очень просто:

Теперь я получаю другую ошибку. Я даже не могу загрузить страницу и каждый раз, когда я получаю «Не удалось найти представление„ошибки“в каталоге просмотров . »

Я даже не знаю, где я должен искать проблемы. Что вызывает ошибку? Мне действительно нужен вид двигатель для выполнения маршрутизации с серверной стороной рендерингом? Как я могу сделать это без него? Пожалуйста, обратитесь за дополнительной информацией, если вам нужны некоторые.

Анализ и оптимизация React-приложений

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

Автор материала, перевод которого мы сегодня публикуем, конечно же, шутит. Здесь речь пойдёт о том, как оптимизировать производительность React-приложений. Кстати, прежде чем начать, подумаем о том, зачем вообще нужна оптимизация сайтов. Пожалуй, можно сказать, что нужна она для того, чтобы сайтом могло бы пользоваться больше людей, чем до оптимизации.

Введение

Как оптимизировать сайт? Как можно оценить выгоду от оптимизации для пользователей сайта? И почему вообще нужно задумываться о подобных показателях?

Попытаемся ответить на эти вопросы, взглянув на самый простой способ создания React-приложений — на использование средства create-react-app (CRA). У нового проекта, созданного с помощью этого инструмента, имеются следующие зависимости:

  • Основная библиотека react , которая позволяет работать с компонентами React: 2.5 Кб.
  • Библиотека react-dom , позволяющая выводить компоненты на страницу, превращая их в структуры, подходящие для вставки в дерево DOM: 30.7 Кб.
  • Небольшой объём кода, в который входит и шаблон первого компонента: около 3 Кб.

Эти данные получены для React 16.6.3.

Для того чтобы узнать о том, сколько времени займёт загрузка нового CRA-приложения на телефоне Moto G4, можно воспользоваться сервисом WebPageTest.

Время загрузки сайта на телефоне Moto G4 с использованием разных сетей

Это приложение, разновидность «Hello, World», размещено на хостинге Firebase, исследуется его загрузка в браузере Chrome с использованием трёх типов соединений:

  • 4G (9 Мбит/с)
  • 3G (1.6 Мбит/с)
  • Медленное 3G-соединение (400 Кбит/с)

Тут нужно учитывать и сетевые задержки.

Почему я, для эксперимента, использовал Moto G4? Это — простой недорогой телефон, похожий на те телефоны, которыми, в виде основных устройств, пользуются многие люди в развивающихся странах. В 4G-сети приложение загрузилось за 2 секунды. В медленной 3G-сети для того, чтобы страница вышла в интерактивный режим работы, понадобилось более 4 секунд.

Хотя эти показатели и довольно интересны, они не особенно полезны в том случае, если вы не знаете о том, кем являются ваши пользователи. То, что вы определяете как «медленно», может полностью отличаться от того, что «медленным» считаю я или кто-то ещё. И ваше восприятие «быстрой» загрузки сайта может быть искажено тем, каким устройством и каким сетевым соединением вы пользуетесь. Если включить в этот эксперимент настольный компьютер, подключённый к интернету по проводному соединению, можно увидеть, как велико может быть различие между «быстрой» и «медленной» загрузкой сайта.

Время загрузки сайта на настольном компьютере и на Moto G4

Для того чтобы повысить производительность React-приложений, при построении которых используется библиотека React, что называется, «из коробки», намечены улучшения React DOM, которые нацелены на упрощение некоторых вещей. Так, система событий содержит множество полифиллов, которые, для многих новых браузеров, не нужны, и команда разработчиков рассматривает варианты их удаления или упрощения в тех случаях, если это возможно. Понаблюдать за этим можно здесь.

Можно ли измерить текущий уровень производительности веб-сайтов?

Типичное React-приложение может содержать множество компонентов и библиотек сторонних разработчиков. Это означает, что производительность «Hello, World»-приложения не даёт нам особенно ценных сведений о том, как загружаются настоящие приложения. Есть ли способ узнать о том, насколько высокопроизводительным является большинство сайтов, построенных с использованием некоей технологии (вроде React)?

Ответить на этот вопрос нам, возможно, может помочь ресурс HTTP Archive. Это — опенсорсная платформа, которая ориентирована на наблюдение за тем, как построен веб. Делается это путём ежемесячного обхода миллионов сайтов, анализа их с помощью WebPageTest и записи сведений о них. В эти сведения входит число запросов, метрики, касающиеся загрузки данных, размеры передаваемых данных и другие показатели.

Вот ещё один интересный инструмент — расширение для Chrome, которое называется Library-Detector-for-Chrome. Оно позволяет выяснять то, какие JavaScript-библиотеки используются на странице. Оно было недавно включено, в качестве инструмента аудита страниц, в Lighthouse. Это говорит о том, что информация, которую даёт это расширение, может быть получена для множества сайтов, сведения о которых хранятся в HTTP Archive. Это может помочь тем, кто хочет проанализировать результаты испытания тысяч сайтов, на которых используется конкретная JavaScript-библиотека (механизм определения React находится здесь).

Полный набор данных HTTP Archive общедоступен, найти его можно на BigQuery. После исследования 140000 сайтов, использующих React, на предмет их загрузки в искусственно смоделированной мобильной среде (набор данных 2020_01_01 ), удалось выяснить следующее:

  • Медиана показателя First Meaningful Paint (первое значимое отображение, время отрисовки важных элементов) составила 6.9 с.
  • Медиана показателя Time to Interactive (время до интерактивности, время загрузки элементов взаимодействия) составила 19.7 с.

Вы можете исследовать эти данные и самостоятельно.

Топ-пост этого месяца:  Основные тренды веб-дизайна в 2020 году

Почти 20 секунд нужно на то, чтобы пользователь смог бы начать взаимодействовать с сайтом. И это, в общем-то, происходит, так сказать, здесь и сейчас, хотя и может выглядеть неправдоподобно. Такое можно видеть на крупных сайтах при работе с ними со слабых устройств на медленных линиях связи. Кроме того, сейчас мне хотелось бы озвучить несколько причин, по которым эти данные стоит рассматривать с долей скептицизма.

  • На производительность сайтов влияет множество факторов. Среди них — объём JavaScript-кода, отправляемого пользователю, число изображений и других материалов, выводимых на странице, и так далее. Некорректно будет сравнивать производительность любых сайтов, основанных на React, с производительностью страницы с надписью «Hello, World» в том случае, если во внимание не принимаются другие факторы.
  • Если попытаться получить данные, заменив в запросе React на название какой-нибудь другой библиотеки, будут получены очень похожие цифры.

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

Рост объёмов JavaScript-кода

Общая проблема современных веб-сайтов, не привязанная к конкретной библиотеке, связана с объёмом JavaScript-кода, которое обычно приходится загружать клиенту при просмотре страниц. На ресурсе HTTP Archive уже имеется хороший отчёт об этом. Если в двух словах, то вот как выглядят медианные значения объёмов JavaScript, загружаемых с веб-сайтов в разные годы:

  • 74.7 Кб — мобильные веб-страницы, 15 декабря 2011 года.
  • 384.4 Кб — мобильные веб-страницы, 15 декабря 2020 года.

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

  • 381.5 Кб — мобильные веб-страницы, 15 декабря 2020 года (вот запрос).

Всё это позволяет сделать вывод о том, что в наши дни создаются веб-сайты, которые включают в себя больше JS-кода, чем сайты, которые создавались несколько лет назад. Это — важное наблюдение. Сайты стали больше, они стали более интерактивными и более сложными, и объём JS-кода этих сайтов постепенно, с каждым годом, растёт. Вероятно, вы уже об этом слышали, но чем больше JavaScript-кода вы отправляете в браузер — тем больше времени нужно на то, чтобы его распарсить, скомпилировать и выполнить. Это, в результате, замедляет сайт.

Важно отметить, что каждый сайт уникален, равно как и пользовательская база каждого сайта. Многие разработчики, сайты которых включают в себя более 300 Кб JS-кода, не сталкиваются с тем, что большинство их пользователей страдает от проблем с производительностью, и это совершенно нормально. Однако если вы беспокоитесь о том, что с просмотром вашего React-сайта у ваших пользователей могут возникать сложности, для того, чтобы оценить реальное положение дел, лучше всего начать с профилирования.

Профилирование и анализ страниц

Профилирование и анализ React-приложений может рассматриваться с двух точек зрения:

  • Во-первых, речь идёт об оценке производительности компонентов. Это влияет на то, как пользователи взаимодействуют с сайтом. Например, если при щелчке по кнопке выводится список, это должно выполняться быстро, но, в том случае, если в ходе выполнения этой операции выполняется повторный рендеринг сотен компонентов, при том, что в нём нет необходимости, эта операция будет восприниматься как медленная.
  • Во-вторых, речь идёт о том, как скоро приложение приводится в рабочее состояние. То есть о том, через сколько времени после начала загрузки сайта пользователь сможет с ним взаимодействовать. Объём кода, отправляемый пользователю в ходе загрузки первой страницы сайта — это пример фактора, влияющего на то, как быстро пользователь сможет начать работу с приложением.

Оценка производительности и оптимизация компонентов

Попытаемся выразить в одном предложении смысл алгоритма реконсиляции React, или суть того, что называют «виртуальной DOM». Это будет выглядеть так: «React предпринимает действия для нахождения различий между новым деревом DOM и старым деревом для того, чтобы понять, что именно в пользовательском интерфейсе должно быть обновлено при изменении данных в компоненте». Это создаёт гораздо меньшую нагрузку на систему, чем повторный рендеринг всего приложения при каждом изменении состояния или свойств (тут можно почитать о разнице между O(n 3 ) и O(n)). Вот статья Дэна Абрамова , где можно найти пояснения по поводу реконсиляции.

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

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

  • Панель Performance инструментов разработчика Chrome.
  • Профилировщик инструментов разработчика React.

▍Анализ производительностью с использованием панели Performance инструментов разработчика Chrome

React использует API User Timing для того чтобы измерять время, приходящееся на каждый шаг жизненного цикла компонента. Сведения о производительности React-приложений можно собирать и анализировать с помощью инструментов разработчика Chrome. Это позволяет понять то, насколько эффективно компоненты подключаются, выводятся на страницу и отключаются в ходе взаимодействия пользователя со страницей или при её перезагрузках.

Анализ производительности компонентов

Вот хороший материал на эту тему, посвящённый исследованию производительности приложений, написанных с использованием React 16, с применением инструментов разработчика Chrome.

API User Timing используется лишь в ходе разработки. Оно, в продакшне, отключается. В таких условиях могут использоваться более быстрые реализации подобных механизмов, не оказывающие серьёзного влияния на производительность. Именно необходимость в таких механизмах и стала одной из причин разработки более нового API Profiler.

▍Анализ производительности с помощью профилировщика из инструментов разработчика React

С выходом библиотеки react-dom 16.5 в инструментах разработчика React можно пользоваться новой панелью, называемой Profiler. Она позволяет анализировать производительность рендеринга компонентов. Делается это с помощью API Profiler, средствами которого собирается информация о времени выполнения операций для всех компонентов, подвергаемых повторному рендерингу.

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

Сбор данных с помощью инструментов разработчика React

После окончания сбора данных будет выведен так называемый «пламенный график», показывающий время, необходимое для рендеринга компонентов на странице.

Пламенный график профилировщика

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

Просмотр сведений о коммитах в профилировщике

Представленные здесь скриншоты представляют данные, полученные в результате записи действий пользователя, выполняемых в простом приложении. Приложение выполняет загрузку списка трендовых GitHub-репозиториев при щелчке по кнопке. Как видите, здесь имеется всего два коммита:

  • Один — для индикатора загрузки, который выводится в ходе загрузки списка элементов.
  • Ещё один представляет момент, когда вызов к API завершён и список выводится в DOM.

На рисунке, в его правой части, показаны полезные метаданные, включающие в себя сведения о коммите или данные компонента, такие, как свойства и состояние.

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

Для того чтобы немного усложнить наш пример, рассмотрим похожую ситуацию, но теперь будем выполнять множество обращений к API для загрузки трендовых репозиториев по разным языкам программирования (вроде Golang, JavaScript, и так далее). Как можно ожидать, при таком подходе в нашем распоряжении оказывается больше коммитов.

Увеличение числа коммитов при усложнении схемы работы с приложением

Более поздние коммиты отличаются более длинными графиками, в них больше жёлтого цвета. Это означает, что время, необходимое всем компонентам для завершения рендеринга, растёт по мере роста размера списка элементов на странице. Это происходит из-за того, что каждый компонент в списке претерпевает повторный рендеринг с каждым новым обращением к API. Это помогает выявить проблему, которую можно довольно просто решить. Решение заключается в том, что существующие в списке элементы не нужно рендерить повторно при добавлении в список новых элементов.

▍Минимизация ненужных операций повторного рендеринга компонентов

Существует немало способов избавления от ненужных операций повторного рендеринга React-компонентов, или, по крайней мере, сведения числа таких операций к минимуму. Рассмотрим некоторые из них.

    Можно воспользоваться методом жизненного цикла компонента shouldComponentUpdate():

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


Для функциональных компонентов можно использовать memo:

  • Можно произвести мемоизацию селекторов Redux (например, с помощь reselect).
  • Можно оптимизировать вывод очень длинных списков, применив виртуализацию (например — с помощью react-window).
  • Вот и вот — пара полезных видео, где рассматривается применение профилировщика React для поиска узких мест в приложениях.

    Оценка производительности и оптимизация приложений

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

    Протестировать веб-страницу с помощью Lighthouse можно тремя способами:

    • Используя интерфейс командной строки Node.js.
    • Используя расширение Chrome.
    • С помощью панели Audits инструментов разработчика Chrome.

    Вот как выглядит Lighthouse на панели Audits.

    Lighthouse на панели Audits

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

    Для того чтобы понять, что загрузка страницы в браузер предусматривает и загрузку слишком большого объёма JavaScript-кода, и сделать вывод о том, что объём этого кода стоит уменьшить, нужно обратить внимание на следующие фразы из отчёта:

    • Eliminate render-blocking resources
    • JavaScript boot-up time is too high
    • Avoid enormous network payloads

    Если Lighthouse сообщает об этих проблемах из-за того, что страница использует JS-бандл слишком большого размера, самым первым, что стоит рассмотреть в качестве способа исправления проблемы, является разделение бандла. Дело в том, что если код можно разбить на фрагменты, некоторые из которых нужны только для работы с определёнными страницами сайта, то у нас нет причин этой возможностью не воспользоваться.

    ▍Разделение JS-бандлов

    Один из способов разделения кода на части является использование динамического импорта:

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

    Приложение для сортировки чисел

    В этом примере происходит следующее:

    1. Пользователь щёлкает по кнопке для того, чтобы отсортировать три числа.
    2. Импортируется lodash.sortby .
    3. Вызывается метод doSomethingCool() , который сортирует числа и выводит их в новом узле DOM.

    Это — предельно простой, искусственный пример, так как если кому-то понадобится сортировать числа на веб-странице, то он, вероятно, просто воспользуется методом Array.prototype.sort() . Но хочется надеяться, что этот пример помог мне показать то, почему использование динамического импорта при выполнении пользователем неких действий может оказаться полезным.

    Синтаксис динамического импорта является сравнительно новым, он в настоящее время находится на третьей стадии процесса принятия новых возможностей JavaScript комитетом TC39. Этот синтаксис уже поддерживается в Chrome и в Safari, а также бандлерами Webpack, Rollup и Parcel.
    Если говорить о React, то тут существуют абстракции, созданные для упрощения процесса разбиения кода на уровне компонентов с использованием технологии динамического импорта. Один из примеров реализации этого механизма — React.lazy :

    Одной из основных проблем, связанных с асинхронной загрузкой различных частей приложения, является работа с задержками в работе приложения, с которыми может столкнуться пользователь. Для этого существует компонент Suspense , который может быть использован для того, чтобы «приостановить» вывод на экран дерева некоего компонента. При использовании его вместе с React.lazy , можно, если код нужного компонента всё ещё загружается, показать пользователю индикатор загрузки:

    Компонент Suspense пока ещё не работает при применении серверного рендеринга. Если вы хотите воспользоваться методиками разделения кода в React-приложениях, которые рендерятся на сервере, используйте, следуя совету, данному в документации React, библиотеку наподобие loadable-components .

    LoadingComponent может быть использован с loadable-component во время загрузки основного компонента как индикатор.

    Обратите внимание на то, что для того, чтобы использовать библиотеку loadable-components в серверном рендеринге, нужно кое-что настроить.

    Подумаем над тем, где именно в приложении стоит пользоваться техниками разделения кода. Пожалуй, легче всего разделять код, основываясь на маршрутах. В документации React есть пояснения о том, как это может выглядеть с использованием маршрутизатора React и Suspense .

    ▍Стоит ли заниматься разделением кода, которое опирается на прокрутку страницы?

    Вот ещё одна интересная библиотека для разделения кода, react-loadable-visibility, которая построена на базе библиотеки loadable-components и веб-API Intersection Observer. Её можно использовать для загрузки компонентов по мере того, как они становятся видимыми при прокрутке страницы.

    ▍О кэшировании того, что стоит кэшировать

    Сервис-воркеры — это веб-воркеры, которые выполняются в фоне браузера во время просмотра страницы. Основная цель сервис-воркеров заключается в том, чтобы реализовывать в них некий функционал, выполняющийся в отдельном потоке и способный улучшить впечатления пользователя от работы с сайтом. Сюда входит кэширование важных файлов, что приводит к тому, что когда пользователь повторно посещает сайт, браузер может загрузить что-то, обратившись к сервис-воркеру, а не к серверу, что способно ускорить загрузку страниц.

    Ускорение загрузки страниц с использованием кэширования

    Workbox — это набор библиотек, который упрощает включение в проект сервис-воркеров, избавляя программиста от необходимости писать весь необходимый код с нуля. С использованием CRA 2.0 для того, чтобы воспользоваться этим механизмом, достаточно удалить всего пару символов из файла src/index.js . Это даст вам возможность задействовать стандартный сервис-воркер с базовыми возможностями по кэшированию.

    Подробности о сервис-воркерах и о библиотеке Workbox вы можете найти в этом материале.

    ▍Серверный рендеринг и потоковая передача данных

    В основе технологий серверного рендеринга веб-сайтов лежит идея снижения нагрузки на устройства пользователей за счёт отправки в браузеры готового HTML-кода, сгенерированного на сервере, точно такого же, который получился бы в браузере в результате работы механизмов программного формирования страницы. Это позволяет сделать так, чтобы пользователи видели бы содержимое страниц гораздо быстрее, чем в том случае, когда им, чтобы сформировать страницу, сначала нужно загрузить некий JS-код, а потом дождаться окончания его выполнения.

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

    Работая с React 16, можно воспользоваться возможностями потоковой передачи данных при серверном рендеринге компонентов. Вместо того чтобы пользоваться методом renderToString() для формирования HTML-строки, можно использовать метод renderToNodeStream() для возврата Readable-потока Node.

    Потоковая передача данных с сервера позволяет клиенту получать и по очереди выводить разные части HTML-документа вместо того, чтобы выводить сразу весь документ. Хотя серверный рендеринг всегда улучшает показатель, связанный с первым выводом страницы приложения на экран, этот подход способен ещё сильнее улучшить данный показатель.

    Если вы применяете React для разработки статических сайтов, то, вместо вышеупомянутого метода, используйте метод renderToStaticNodeStream.

    ▍Использование предварительного рендеринга в тех случаях, когда серверный рендеринг неприменим

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

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

    Если вы хотите использовать эту технологию в своём приложении, вам могут в этом помочь библиотеки наподобие react-snap, которые пользуются возможностями Puppeteer.

    Вот хороший материал, посвящённый различным подходам к серверному рендерингу.

    ▍Об извлечении важных стилей, описанных в виде CSS-in-JS

    Многие React-разработчики, по разным причинам, используют CSS-in-JS-библиотеки вроде emotion и styled-components. Среди таких причин можно отметить возможность использования стилей, область действия которых ограничена компонентом, автоматическое создание селекторов, основанных на свойствах, передаваемых компонентам, и другие. Если при использовании технологии CSS-in-JS не проявлять должную осмотрительность, можно столкнуться с проблемой, которая заключается в том, что все стили будут вычисляться во время выполнения кода. Это означает, что стили будут применяться только после того, как JavaScript-бандл страницы будет выполнен, что приводит к тому, что пользователь может некоторое время наблюдать нестилизованное содержимое страницы. Как и в случае с любыми другими проблемами, касающимися производительности, эта проблема усиливается в тех случаях, когда для работы с сайтом используются слабые мобильные устройства или медленные сетевые соединения.

    Ситуация, в которой пользователь некоторое время видит нестилизованное содержимое страницы (взято отсюда)

    Если в вашем приложении уже используется какая-нибудь разновидность серверного рендеринга, исправить проблему со стилями можно путём извлечения самых важных из них. Библиотеки emotion и styled-components поддерживают эту возможность. Стили можно извлечь в Readable-поток Node. У Glamor есть отдельная библиотека, которая позволяет делать то же самое.

    Извлечение важных стилей может значительно улучшить ситуацию для пользователей, работающих на слабых устройствах или с медленными сетевыми соединениями.

    Тут показано Preact-приложение, но этот пример позволяет оценить возможности по извлечению стилей (источник)

    Если всё, что вам нужно от технологии CSS-in-JS — это использование стилей, область действия которых ограничена компонентами, и вы при этом не хотите перегружать приложение библиотеками, реализующими более широкий функционал, возможно, вам подойдёт библиотека astroturf. Используя её, вы получите не все возможности, доступные в типичной библиотеке такого рода, но в вашем распоряжении будет возможность работать с управляемыми стилями компонентов, использование которых не создаст дополнительной нагрузки на систему во время выполнения кода приложения.

    ▍Доступность сайтов

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

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

    Кроме того, вы можете рассмотреть возможность использования react-axe для проведения аудита готового, отрендеренного приложения, а не только его JSX-кода.

    ▍Улучшение возможностей по добавлению сайтов на домашний экран

    Хорошо ли ваш сайт работает на мобильных устройствах? Есть ли у вас сервис-воркеры, которые кэшируют некоторые материалы, что позволяет приложению работать и без подключения к интернету? Является ли ваше приложение настолько полезным, что пользователям может захотеться разместить значок для быстрого доступа к нему на домашнем экране их устройств?

    Если вы отвечаете на все эти вопросы положительно, тогда вам нужно написать для своего сайта манифест веб-приложения. Это повысит удобство пользователей по работе с приложением при его «установке» на их мобильные устройства. Манифест позволяет много всего настраивать, в том числе — значок приложения, фоновый цвет и цвет темы.

    При использовании create-react-app создаваемый этим инструментом шаблон приложения оснащается стандартным файлом-манифестом:

    Разное

    Вот некоторые идеи, которые направлены на оптимизацию труда программистов. Их применение, кроме того, ведёт к снижению объёма кода приложений. Чем меньше объём кода приложения — тем лучше. Почему это так? Дело в том, что чем компактнее будет код приложения, тем меньше данных придётся передавать с сервера в браузер и тем быстрее будут загружаться страницы приложения.

    ▍Atomic CSS

    Методика использования атомарных CSS-стилей (Atomic CSS) предусматривает создание классов, решающих чрезвычайно узкие задачи и отличающихся маленькими размерами. Например, при таком подходе отдельный класс используется для того, чтобы назначить кнопке синий фон:

    Отдельный класс используется для настройки свойства кнопки padding :

    И так далее. Хотя это приводит к необходимости добавлять очень большое количество значений в атрибут class большинства узлов DOM, это даёт преимущество, заключающееся в том, что разработчику приходится писать гораздо меньше CSS-кода, чем обычно. Достигается это за счёт использования библиотек, ответственных за автоматическую настройку селекторов. Одной из таких библиотек является Tachyons.

    Ограничение области действия стилей границами компонента с использованием атомарных классов стало привычной практикой для многих разработчиков. А библиотеки вроде tachyon-components даже позволяют применять эти стили с использованием API, похожего на API styled-components :

    Хотя это может и не быть основной причиной, по которой разработчики используют библиотеки для работы с атомарными стилями, устранение необходимости в создании новых CSS-селекторов благодаря использованию фиксированного набора «одноразовых» классов означает, что объём CSS-кода, отправляемого в браузер, всегда будет оставаться неизменным.

    Хуки позволяют делать с функциональными компонентами много всего такого, что раньше было доступно только при работе с компонентами, основанными на классах. Работа с состоянием компонента — это лишь один из примеров:

    Хуки можно использовать для решения следующих задач:

    • Включение в компонент состояния без использования компонентов, основанных на классах (useState).
    • Включение в компонент побочных эффектов без использования компонентов, основанных на классах (useEffect).
    • Повторное использование одной и той же логики в разных компонентах благодаря созданию собственных хуков.

    Всё это позволяет включать в компоненты логику, которая может быть использована повторно в других местах приложения. До появления хуков для того, чтобы пользоваться некоторыми из этих возможностей, применялась библиотека recompose.

    Вот запись с мероприятия React Conf, в начале которой можно найти подробное разъяснение концепции хуков.

    Хотя замечательные возможности хуков и сами по себе приведут к их широкому распространению, можно отметить, что их использование способно помочь и в деле уменьшения размеров пакетов JavaScript-кода приложений. Как пишет Дэн Абрамов , если говорить об увеличении библиотеки React после добавления в неё поддержки хуков, то оно составило примерно 1.5 Кб (минифицированная и сжатая версия). Весьма вероятно то, что применение хуков поможет уменьшить размер бандлов из-за того, что код, в котором используются хуки, лучше, чем код, в котором используются компоненты, основанные на классах, поддаётся минификации.

    Итоги

    Здесь мы рассмотрели множество вопросов, касающихся оптимизации производительности React-приложений. Вот краткая сводка по тем шагам, которые может предпринять тот, кто стремится сделать свои приложения быстрее:

      Измерьте производительность компонентов приложения, воспользовавшись одним из следующих инструментов:

    • Панель Performance инструментов разработчика Chrome.
    • Профилировщик инструментов разработчика React.
  • Минимизируйте число ненужных операций повторного рендеринга компонентов

    • Там, где это применимо, воспользуйтесь методом жизненного цикла компонента shouldComponentUpdate() .
    • Для компонентов, основанных на классах, используйте PureComponent .
    • Для функциональных компонентов используйте React.memo .
    • Воспользуйтесь технологией мемоизации селекторов Redux (например, с помощью reselect ).
    • Примените технологии виртуализации при выводе очень длинных списков (например, с помощью react-window ).
  • Измерьте общую производительность приложения с помощью Lighthouse.
  • Улучшите общую производительность приложения, применив следующие подходы к оптимизации:

    • Если вы не пользуетесь серверным рендерингом — подумайте о разбиении кода компонентов с помощью React.lazy .
    • Если вы пользуетесь серверным рендерингом — разделяйте код компонентов с использованием библиотеки наподобие loadable-components .
    • Используйте сервис-воркеры для кэширования материалов, которые стоит кэшировать. Вам в этом может серьёзно помочь Workbox .
    • Если вы пользуетесь серверным рендерингом — используйте потоки вместо строк (с помощью renderToNodeStream и renderToStaticNodeStream ).
    • Серверный рендеринг вам не подходит? Тогда задействуйте предварительный рендеринг с использованием библиотек наподобие react-snap .
    • Извлекайте самые важные стили при использовании библиотек, нацеленных на реализацию технологии CSS-in-JS.
    • Постарайтесь сделать так, чтобы ваши приложения были бы доступны как можно более широкому кругу пользователей. Рассмотрите возможность использования библиотек React A11y и react-axe .
    • Оснастите свой проект манифестом веб-приложения в том случае, если полагаете, что мобильным пользователям будет удобно работать с ним, вызывая его с домашнего экрана их устройств.
    Топ-пост этого месяца:  Зеркала, дубли страниц и Url адреса — аудит вашего сайта или что может быть причиной краха при его
  • Когда опенсорсный проект, наподобие React, приобретает огромнейшую популярность, вполне естественно ожидать от него следующего:

    • Улучшение его API, идущее через создание средств, упрощающих создание приложений.
    • Появление библиотек сторонних разработчиков, которые, опять же, упрощают создание приложений.

    Фраза «упрощение создания приложений» может означать очень много всего. Сюда относится и облегчение разработки веб-проектов, которые быстро загружаются на устройства пользователей и быстро работают.

    Уважаемые читатели! Как вы оптимизируете свои React-приложения?

    Укрощаем изоморфный и универсальный шаблон: серверный рендеринг на React-Redux

    Серверный рендеринг с помощью React Redux

    Ищите шаблон на React-Redux? Если да, то вряд ли у вас проблемы с поиском. Потому что вы нашли ТОННЫ шаблонов. Andrew Farmer

    Вам также может понравиться

    С момента релиза React и Redux я находился на седьмом небе от счастья потому что 1) Изоморфность React дает преимущества к скорости загрузки и SEO при серверном рендеренге и позволяет рендерить компоненты на клиенте уже после загрузки страницы и 2) Идеи Flux-архитектуры расширяют компоненты React с помощью использования однонаправленного потока данных.

    Однако, после анализа тысячи шаблонов, мне пришлось выпить не одну чашку чая, чтобы успокоиться. Потому что ни один из шаблонов не оказался простым в освоении. Так что мне пришлось изучать разработку шаблонов с нуля. Эта статья представляет собой заметку, где я пояснил, как разбить шаблон на понятные части шаг за шагом. В конце концов, я выделил пять пунктов и они являются причиной такого большого разнообразия изоморфных шаблонов:

    1. Dev-сервер и сборка проекта
      Настройка и создание среды разработки для решения таких проблем, как горячее обновление ( HMR), слежение за изменением файлов, сборка бандлов и так далее. Популярные инструменты для этого: webpack, gulp, nodenpm, broserify, grunt.
    2. React-Redux-компоненты и поток данных
    3. Роутинг
      Чтобы решить, какую иерархию роутинга использовать, нужно ответить на вопросы: это SPA? Должен ли сервер иметь CORS? Может быть стоит запускать приложение внутри express-сервера, а не на клиенте?
    4. Общие редюсеры/ создатели экшенов
      Зависит от роутинга
    5. Тесты

    Пока будем говорить о втором пункте, потому что он является неизменным от шаблона к шаблону. Остальные пункты меняются в зависимости от предпочтений разработчиков. SSR, общие компоненты и Redux модули — это ядро изоморфной flux-архитектуры — причина того, почему в каждом шаблоне используются пакеты react, redux и react-redux.

    Склонируйте из репозитория helloWorld-пример на React-Redux. Мы будем разбивать его на части, чтобы понять, как работает каждый файл:

    Серверный рендеринг с помощью React Redux

    Выше представлена структура работы приложения. Приложение использует Express, который работает только с одним роутом, отображая с помощью функции res.sendFile index.html в браузере. Особое внимание следует уделить части, обведенной голубым контуром. Там показано взаимодействие React, Redux, корневого компонента, стора и редюсера.

    React-Redux-структура данных, стора, пропсов, стейта и компонентов

    Это пример, предоставленный официальной документацией, Facebook даёт некоторые советы по организации иерархии компонентов: использование «умных» и «глупых» компонентов, использование connect() вместо store.subscribe() и так далее.

    index.js — это вершина иерархии, корневой компонент, содержащий в себе все остальные компоненты в виде виртуального DOM, также это единственный компонент, использующий большое число зависимостей. Помимо этого, другие компоненты также включают в себя сторонние зависимости, обеспечивающие чистоту кода.

    До сих пор я чувствую, как много Facebook сделал для нас, разработчиков.

    Ниже расположены функции, играющие ключевую роль в React-Redux-компонентах:

    Prov >Этот компонент волшебным образом делает стор доступным сразу во всех умных компонентах, не передавая его явно напрямую.

    connect(…)

    Подключает React-компонент к Redux, позволяя компоненту использовать стор верхнего уровня. При этом отпадает необходимость получать стор в виде пропсов от родителя. (взято отсюда).

    Условно работу connect(. ) можно разбить на следующие стадии:

    1. Передача стора внутрь корневого компонента

    Чтобы начать подключать компоненты, нужно обернуть корневой компонент в Provider и передать в него переменную store :

    1. Подключение стора к умным компонентам

    Документация React-Redux описывает множество способов использования connect . Для моей цели понадобятся только mapStateToProps и mapDispatchToProps , чтобы покрыть всю функциональность.

    • преобразование стейта в пропсы ( mapStateToProps ) позволит компоненту Hello использовать this.props.message из Redux-стора.
    • преобразование диспатчинга экшенов ( mapDispatchToProps ) для HELLO_WORLD позволяет использовать this.props.onClick , как функцию внутри компонента Hello.

    NPM модули, которые вы должны знать

    Ниже представлены некоторые из npm модулей, о которых знают не все:

    react-redux

    По умолчанию Redux не включен в React, поэтому пакет react-redux нужно устанавливать дополнительно. Это предполагает, что вы должны использовать сборщики модулей типа Webpack или Browserify, которые работают с CommonJS.

    webpack-dev-m >Это простая миддлвара для Webpack. Служит для обработки файлов, загружающихся из npm-пакетов. Используется только при разработке (узнать больше).

    webpack-hot-server-m >Миддлвара, работающая в паре с webpack-dev-m >HMR) webpack-бандлов на серевере (узнать больше).

    Пояснения

    Ниже раскрыты некоторые термины и концепции:

    Означает горячая замена (обновление) модулей или хотрелоад. Это фишка Webpack, позволяющая обновлять ваш JavaScript-код без перезагрузки браузера (узнать больше).

    combineReducers(…)

    Создает объект, содержащий значение нескольких редюсеров, который затем можно передать в createStore .

    createStore(reducer, [preloadedState], [enhancer])

    1. Создает Redux-стор, хранящий стейт-дерево вашего приложения.
    2. Cоздание функции createStore(reducer, [initialState], [enhancer]) , которая затем передается в

    subscribe( … ) против connect(…)

    По факту эти функции делают одно и то же в Redux, но официальная документация React НЕ советует использовать store.subscribe() , по той причине, что в connect() внесено множество оптимизаций, которые сложно сделать вручную, используя store.subscribe() .

    С помощью connect() создается «умный» компонент, подключаемый к Redux-стору.

    «Умные» компоненты против «Глупых»

    Подробнее о разделении на «умные» и «глупые» здесь

    • «Умные» компоненты предоставляют данные для «глупых» компонентов
    • «Глупые» компоненты: 1) Не имеют зависимостей от остального приложения 2) Определяют визуальную составляющую приложения.

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

    Разработчикам часто приходится принимать решения, которые повлияют на всю архитектуру приложения. Веб-разработчикам важно выбрать правильное место для реализации логики и рендеринга приложения. Это может быть непросто, так как сайт можно создать разными путями, — пишет tproger.ru.

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

    Терминология

    Отображение:

    • SSR (Server-Side Rendering, серверный рендеринг) — рендеринг на сервере клиентской части или универсального приложения в HTML;
    • CSR (Client-Side Rendering, рендеринг на клиенте) — рендеринг приложения на стороне клиента (в браузере), обычно с помощью DOM;
    • Регидратация — «загрузка» JavaScript компонентов на клиенте таким образом, чтобы они повторно использовали DOM-дерево и данные HTML, сформированные на стороне сервера;
    • Пререндеринг — запуск клиентского приложения во время сборки для сохранения его начального состояния в виде статического HTML.

    Производительность:

    • TTFB (Time to First Byte, время до первого байта) — время между кликом по ссылке и поступлением первого фрагмента содержимого;
    • FP (First Paint, первая отрисовка) — первый случай, когда пользователю становится виден любой пиксель;
    • FCP (First Contentful Paint, первая содержательная отрисовка) — время первого отображения запрашиваемого содержимого (например, статьи);
    • TTI (Time To Interactive, время до интерактивности) — момент времени, в который страница становится интерактивной (закрепляются все события и т. д.).

    Серверный рендеринг

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

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

    При использовании серверного рендеринга пользователям не нужно ждать завершения работы JavaScript, отнимающего ресурсы процессора, прежде чем они смогут начать работать с сайтом. Даже если нельзя избежать использования стороннего JavaScript, серверный рендеринг позволяет уменьшить количество вашего собственного JavaScript и даёт больше «бюджета» для всего остального. Однако у этого подхода есть один существенный недостаток: формирование страницы на сервере занимает определённое время, что может привести к большему времени до первого байта.

    Ответ на вопрос «достаточно ли серверного рендеринга для моего приложения?» зависит от того, что вы создаёте. Сообщество давно дискутирует на тему правильного применения серверного рендеринга против клиентского, но важно помнить, что для одних страниц использовать серверный рендеринг можно, а для других — нет. Некоторые сайты успешно используют смешанный рендеринг. Netflix генерирует на сервере относительно статические лендинги и в то же время предварительно загружаетJavaScript для страниц с высоким уровнем интерактивности, давая возможность быстрее загрузиться страницам, которые больше используют клиентский рендеринг.

    Многие современные фреймворки, библиотеки и архитектуры позволяют рендерить одно и то же приложение как на клиенте, так и на сервере. Их возможности можно использовать и для серверного рендеринга, однако важно отметить, что архитектуры, в которых рендеринг происходит и на клиенте, и на сервере, являются отдельным классом решений со своими характеристиками производительности и недостатками. Пользователи React могут использовать для серверного рендеринга метод renderToString() или решения на его основе вроде Next.js. Пользователям Vue стоит обратить внимание на гайд Vue по серверному отображению или на Nuxt. Если ваш выбор — Angular, то посмотрите на Universal. Тем не менее в большинстве популярных решений присутствует какая-то форма гидратации, поэтому, прежде чем выбрать инструмент, разузнайте больше о используемом подходе.

    Статический рендеринг

    Статический рендеринг происходит на этапе сборки и предоставляет быструю первую отрисовку, первую содержательную отрисовку и время до интерактивности — при условии, что количество клиентского JavaScript ограничено. В отличие от серверного рендеринга здесь удаётся добиться стабильно быстрого времени до первого байта, так как HTML-код страницы не должен генерироваться на лету. Как правило, статический рендеринг подразумевает предварительное создание отдельного HTML-файла для каждого URL. Поскольку HTML-ответы созданы заранее, статический рендеринг можно развернуть на нескольких CDN, чтобы воспользоваться преимуществом кеширования.

    Для статического рендеринга существуют самые разные решения. Инструменты вроде Gatsby разработаны так, чтобы создавать впечатление динамического рендеринга. Другие, вроде Jekyl и Metalsmith, принимают свою статическую природу и предлагают подход, основанный в большей степени на шаблонах.

    Но у такого способа рендеринга есть один недостаток — необходимо заранее создать HTML-файлы для всех возможных URL. Это может быть очень сложно или даже невыполнимо, если вы не можете заранее сказать, какие URL возможны, или если у вас сайт с большим количеством уникальных страниц.

    Пользователи React могут быть знакомы с Gatsby, статическим экспортом Next.js или Navi — все они делают использование компонентов удобнее. Однако важно понимать разницу между статическим рендерингом и пререндерингом: статически отрендеренные страницы не нуждаются в выполнении большого количества клиентского JS для интерактивности, в то время как пререндеринг улучшает первую (содержательную) отрисовку одностраничного приложения, которое должно быть загружено на клиент, чтобы страницы были действительно интерактивными.

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

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

    Серверный рендеринг против статического

    Серверное отображение не панацея — его динамическая природа может сопровождаться множественными вычислительными затратами. Многие решения с серверным отображением не используют технологию early flush и могут оттянуть время до первого байта или удвоить количество отправляемых данных (например, встроенное состояние, используемое JS на клиенте). В React метод renderToString() может быть медленным из-за синхронности и однопоточности. Для эффективной реализации серверного рендеринга может потребоваться найти решение для кеширования компонентов, разобраться с управлением потребления памяти, применить мемоизациюи не только. По сути вы заново обрабатываете/собираете одно приложение несколько раз — на сервере и на клиенте. Тот факт, что серверный рендеринг может показать что-то быстрее, вовсе не означает, что нужно проделать меньше вычислительной работы.

    Серверный рендеринг создаёт HTML для каждого URL при необходимости, но такой подход может работать медленнее, чем простая отправка статического контента. Если вы готовы поработать дополнительно, то с помощью связки серверного рендеринга с HTML-кешированием вы сможете сильно уменьшить время рендеринга. Преимуществом серверного рендеринга является возможность извлекать больше актуальных данных и отвечать на более полный список запросов, чем это возможно при статическом рендеринге. Персонализированные страницы — яркий пример запроса, который не очень хорошо ладит со статическим рендерингом.


    Клиентский рендеринг

    Клиентский рендеринг подразумевает рендеринг страниц прямо в браузере с помощью JavaScript. Вся логика, получение данных, шаблонизация и маршрутизация обрабатываются на клиенте, а не на сервере.

    При таком рендеринге сложно поддерживать высокую скорость на мобильных устройствах. Можно приблизиться к производительности чистого серверного рендеринга, если выполнять минимум работы, иметь узкий бюджет для JavaScript и доставлять данные с минимальной круговой задержкой. Критические скрипты и данные можно отправить позднее с помощью HTTP/2 Server Push или
    , что говорит парсеру начать работу заранее. Шаблоны вроде PRPL достойны внимания, потому что могут обеспечить ощущение мгновенного первого и последующих переходов между страницами.

    Основной недостаток клиентского рендеринга заключается в том, что количество необходимого JavaScript обычно увеличивается вместе с ростом приложения. Ситуация ухудшается с подключением новых JavaScript-библиотек, полифиллов и прочего стороннего кода, который соревнуется между собой за вычислительные мощности и часто требует обработки, прежде чем содержимое страницы можно будет отобразить. Решениям с клиентским рендерингом, которые полагаются на большие JavaScript-бандлы, стоит рассмотреть сильное разделение кода и ленивую загрузку JavaScript — «загружайте только то, что вам нужно и только когда это нужно». Для решений с минимумом интерактивности или её отсутствием серверный рендеринг может предоставить более масштабируемое решение этих проблем.

    Если вы создаёте одностраничное приложение, то, определив основные части пользовательского интерфейса, которые используются на большинстве страниц, вы сможете использовать кеширование оболочки приложения. В сочетании с Service Worker’ами это даст сильный прирост ощущаемой производительности при повторных посещениях.

    Совмещение серверного и клиентского рендеринга с помощью регидратации

    Универсальный рендеринг (или просто «SSR») пытается устранить недостатки серверного и клиентского рендеринга, используя оба подхода. Навигационные запросы вроде полной загрузки или перезагрузки страницы обрабатываются сервером, который рендерит приложение в HTML, затем JavaScript и данные, используемые для рендеринга, встраиваются в итоговый документ. При правильной реализации время первой содержательной отрисовки будет как при серверном рендеринге, а повторный рендеринг будет производиться на клиенте с помощью техники, называемой (ре)гидратацией. Это новое решение, тем не менее не лишённое определённых проблем с производительностью.

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

    Возможно, вы и сами сталкивались с таким — страница уже какое-то время выглядит загруженной, но нажатия на элементы не дают эффекта. Это сильно удручает: «Почему ничего не происходит? Почему я не могу скроллить?».

    Проблема регидратации: одно приложение по цене двух

    Из-за JavaScript проблемы регидратации могут быть похуже, чем отложенная интерактивность. Для того чтобы клиентский JavaScript мог, не прибегая к новому запросу всех данных, использованных сервером для рендеринга HTML, продолжить работу с того места, где прекратил работу сервер, текущие решения на основе универсального рендеринга обычно сериализуют данные для интерфейса в документ в виде тегов

    Новшества серверного рендеринга в React 16

    Вышел React 16! Рассказывая об этом событии, можно упомянуть множество замечательных новостей (вроде архитектуры ядра Fibers), но лично меня больше всего восхищают улучшения серверного рендеринга. Предлагаю подробно всё это разобрать и сравнить с тем, что было раньше. Надеюсь, серверный рендеринг в React 16 понравится вам так же, как он понравился мне.

    Как работает SSR в React 15

    Для начала вспомним, как серверный рендеринг (Server-Side Rendering, SSR) выглядит в React 15. Для выполнения SSR обычно поддерживают сервер, основанный на Node, использующий Express, Hapi или Koa, и вызывают renderToString для преобразования корневого компонента в строку, которую затем записывают в ответ сервера:

    Когда клиент получает ответ, клиентской подсистеме рендеринга, в коде шаблона, отдают команду восстановить HTML, сгенерированный на сервере, используя метод render() . Тот же метод используют и в приложениях, выполняющих рендеринг на клиенте без участия сервера:

    Если сделать всё правильно, клиентская система рендеринга может просто использовать HTML, сгенерированный на сервере, не обновляя DOM.

    Как же SSR выглядит в React 16?

    Обратная совместимость React 16

    Команда разработчиков React показала чёткую ориентацию на обратную совместимость. Поэтому, если ваш код выполняется в React 15 без сообщений о применении устаревших конструкций, он должен просто работать в React 16 без дополнительных усилий с вашей стороны. Код, приведённый выше, например, нормально работает и в React 15, и в React 16.

    Если случится так, что вы запустите своё приложение на React 16 и столкнётесь с ошибками, пожалуйста, сообщите о них! Это поможет команде разработчиков.

    Метод render() становится методом hydrate()

    Надо отметить, что переходя с React 15 на React 16, вы, возможно, столкнётесь со следующим предупреждением в браузере.

    Очередное полезное предупреждение React. Метод render() теперь называется hydrate()

    Оказывается, в React 16 теперь есть два разных метода для рендеринга на клиентской стороне. Метод render() для ситуаций, когда рендеринг выполняются полностью на клиенте, и метод hydrate() для случаев, когда рендеринг на клиенте основан на результатах серверного рендеринга. Благодаря обратной совместимости новой версии React, render() будет работать и в том случае, если ему передать то, что пришло с сервера. Однако, эти вызовы следует заменить вызовами hydrate() для того, чтобы система перестала выдавать предупреждения, и для того, чтобы подготовить код к React 17. При таком подходе код, показанный выше, изменился бы так:

    React 16 может работать с массивами, строками и числами

    В React 15 метод компонента render() должен всегда возвращать единственный элемент React. Однако, в React 16 рендеринг на стороне клиента позволяет компонентам, кроме того, возвращать из метода render() строку, число, или массив элементов. Естественно, это касается и SSR.

    Итак, теперь можно выполнять серверный рендеринг компонентов, который выглядит примерно так:

    Можно даже передать строку, число или массив компонентов методу API верхнего уровня renderToString :

    Это должно позволить вам избавиться от любых div и span , которые просто добавлялись к вашему дереву компонентов React, что ведёт к общему уменьшению размеров HTML-документов.

    React 16 генерирует более эффективный HTML

    Если говорить об уменьшении размеров HTML-документов, то React 16, кроме того, радикально снижает излишнюю нагрузку, создаваемую SSR при формировании HTML-кода. В React 15 каждый HTML-элемент в SSR-документе имеет атрибут data-reactid , значение которого представляет собой монотонно возрастающие ID, и текстовые узлы иногда окружены комментариями с react-text и ID. Для того, чтобы это увидеть, рассмотрим следующий фрагмент кода:

    В React 15 этот фрагмент сгенерирует HTML-код, который выглядит так, как показано ниже (переводы строк добавлены для улучшения читаемости кода):

    В React 16, однако, все ID удалены из разметки, в результате HTML, полученный из такого же фрагмента кода, окажется значительно проще:

    Такой подход, помимо улучшения читаемости кода, может значительно уменьшить размер HTML-документов. Это просто здорово!

    React 16 поддерживает произвольные атрибуты DOM

    В React 15 система рендеринга DOM была довольно сильно ограничена в плане атрибутов HTML-элементов. Она убирала нестандартные HTML-атрибуты. В React 16, однако, и клиентская, и серверная системы рендеринга теперь пропускают произвольные атрибуты, добавленные к HTML-элементам. Для того, чтобы узнать больше об этом новшестве, почитайте пост Дэна Абрамова в блоге React.

    SSR в React 16 не поддерживает обработчики ошибок и порталы

    В клиентской системе рендеринга React есть две новых возможности, которые, к сожалению, не поддерживаются в SSR. Это — обработчики ошибок (Error Boundaries) и порталы (Portals). Обработчикам ошибок посвящён отличный пост Дэна Абрамова в блоге React. Учитывайте однако, что (по крайней мере сейчас) обработчики не реагируют на серверные ошибки. Для порталов, насколько я знаю, пока даже нет пояснительной статьи, но Portal API требует наличия узла DOM, в результате, на сервере его использовать не удастся.

    React 16 производит менее строгую проверку на стороне клиента

    Когда вы восстанавливаете разметку на клиентской стороне в React 15, вызов ReactDom.render() выполняет посимвольное сравнение с серверной разметкой. Если по какой-либо причине будет обнаружено несовпадение, React выдаёт предупреждение в режиме разработки и заменяет всё дерево разметки, сгенерированной на сервере, на HTML, который был сгенерирован на клиенте.

    В React 16, однако, клиентская система рендеринга использует другой алгоритм для проверки правильности разметки, которая пришла с сервера. Эта система, в сравнении с React 15, отличается большей гибкостью. Например, она не требует, чтобы разметка, созданная на сервере, содержала атрибуты в том же порядке, в котором они были бы расположены на клиентской стороне. И когда клиентская система рендеринга в React 16 обнаруживает расхождения, она лишь пытается изменить отличающееся поддерево HTML, вместо всего дерева HTML.

    В целом, это изменение не должно особенно сильно повлиять на конечных пользователей, за исключением одного факта: React 16, при вызове ReactDom.render() / hydrate() , не исправляет несовпадающие HTML-атрибуты, сгенерированные SSR. Эта оптимизация производительности означает, что вам понадобится внимательнее относиться к исправлению несовпадений разметки, приводящих к предупреждениям, которые вы видите в режиме development .

    React 16 не нужно компилировать для улучшения производительности

    В React 15, если вы используете SSR в таком виде, в каком он оказывается сразу после установки, производительность оказывается далеко не оптимальной, даже в режиме production . Это так из-за того, что в React есть множество замечательных предупреждений и подсказок для разработчика. Каждое из этих предупреждений выглядит примерно так:

    К сожалению, оказывается, что process.env — это не обычный объект JavaScript, и обращение к нему — операция затратная. В итоге, даже если значение NODE_ENV установлено в production , частая проверка переменной окружения ощутимо замедляет серверный рендеринг.

    Для того, чтобы решить эту проблему в React 15, нужно было бы скомпилировать SSR-код для удаления ссылок на process.env , используя что-то вроде Environment Plugin в Webpack, или плагин transform-inline-environment-variables для Babel. По опыту знаю, что многие не компилируют свой серверный код, что, в результате, значительно ухудшает производительность SSR.

    В React 16 эта проблема решена. Тут имеется лишь один вызов для проверки process.env.NODE_ENV в самом начале кода React 16, в итоге компилировать SSR-код для улучшения производительности больше не нужно. Сразу после установки, без дополнительных манипуляций, мы получаем отличную производительность.

    React 16 отличается более высокой производительностью

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

    С удовольствием сообщают, что, проведя кое-какие предварительные тесты, я обнаружил значительное увеличение производительности серверного рендеринга React 16 на различных версиях Node:

    Рендеринг на сервере в React 16 быстрее, чем в React 16. Чем ниже столбик — тем результат лучше

    При сравнении с React 16, даже с учётом того, что в React 15 обращения к process.env были устранены благодаря компиляции, наблюдается рост производительности примерно в 2.4 раза в Node 4, в 3 раза — в Node 6, и замечательный рост в 3.8 раза в Node 8.4. Если сравнить React 16 и React 15 без компиляции последнего, результаты на последней версии Node будут просто потрясающими.

    Топ-пост этого месяца:  Анимация в jQuery. Часть 4. Метод animate

    Почему React 16 настолько быстрее, чем React 15? Итак, в React 15 серверные и клиентские подсистемы рендеринга представляли собой, в общих чертах, один и тот же код. Это означает потребность в поддержке виртуального DOM во время серверного рендеринга, даже учитывая то, что этот vDOM отбрасывался как только осуществлялся возврат из вызова renderToString . В результате, на сервере проводилось много ненужной работы.

    В React 16, однако, команда разработчиков переписала серверный рендеринг с нуля, и теперь он совершенно не зависит от vDOM. Это и даёт значительный рост производительности.

    Тут хотелось бы сделать одно предупреждение, касающееся ожидаемого роста производительности реальных проектов после перехода на React 16. Проведённые мной тесты заключались в создании огромного дерева из с одним очень простым рекурсивным компонентом React. Это означает, что мой бенчмарк относится к разряду синтетических и почти наверняка не отражает сценарии реального использования React. Если в ваших компонентах имеется множество сложных методов render , обработка которых занимает много циклов процессора, React 16 ничего не сможет сделать для того, чтобы их ускорить. Поэтому, хотя я и ожидаю увидеть ускорение серверного рендеринга при переходе на React 16, я не жду, скажем, трёхкратного роста производительности в реальных приложениях. По непроверенным данным, при использовании React 16 в реальном проекте, удалось достичь роста производительности примерно в 1.3 раза. Лучший способ понять, как React 16 отразится на производительности вашего приложения — попробовать его самостоятельно.

    React 16 поддерживает потоковую передачу данных

    Последняя из новых возможностей React, о которой хочу рассказать, не менее интересна, чем остальные. Это — рендеринг непосредственно в потоки Node.

    Потоковый рендеринг может уменьшить время до получения первого байта (TTFB, Time To First Byte). Начало документа попадает в браузер ещё до создания продолжения документа. В результате, все ведущие браузеры быстрее приступят к разбору и рендерингу документа.
    Ещё одна отличная вещь, которую может получить от рендеринга в поток — это возможность реагировать на ситуацию, когда сервер выдаёт данные быстрее, чем сеть может их принять. На практике это означает, что если сеть перегружена и не может принимать данные, система рендеринга получит соответствующий сигнал и приостановит обработку данных до тех пор, пока нагрузка на сеть не упадёт. В результате окажется, что сервер будет использовать меньше памяти и сможет быстрее реагировать на события ввода-вывода. И то и другое способно помочь серверу нормально работать в сложных условиях.

    Для того, чтобы организовать потоковый рендеринг, нужно вызвать один из двух новых методов react-dom/server : renderToNodeStream или renderToStaticNodeStream , которые соответствуют методам renderToString и renderToStaticMarkup . Вместо возврата строки эти методы возвращают объект Readable. Такие объекты используются в модели работы с потоками Node для сущностей, генерирующих данные.

    Когда вы получаете поток Readable из методов renderToNodeStream или renderToStaticNodeStream , он находится в режиме приостановки, то есть, рендеринг в этот момент ещё не начинался. Рендеринг начнётся только в том случае, если вызвать read, или, более вероятно, подключить поток Readable с помощью pipe к потоку Writable. Большинство веб-фреймворков Node имеют объект ответа, который унаследован от Writable , поэтому обычно можно просто перенаправить Readable в ответ.

    Скажем, вышеприведённый пример с Express можно было бы переписать для потокового рендеринга следующим образом:

    Обратите внимание на то, что когда мы перенаправляем поток в объект ответа, нам необходимо использовать необязательный аргумент < end: false >для того, чтобы сообщить потоку о том, что он не должен автоматически завершать ответ при завершении рендеринга. Это позволяет нам закончить оформление тела HTML-документа, и, как только поток будет полностью записан в ответ, завершить ответ самостоятельно.

    Подводные камни потокового рендеринга

    Потоковый рендеринг способен улучшить многие сценарии SSR, однако, существуют некоторые шаблоны, которым потоковая передача данных на пользу не пойдёт.

    В целом, любой шаблон, в котором на основе разметки, созданной в ходе серверного рендеринга, формируются данные, которые надо добавить в документ до этой разметки, окажется фундаментально несовместимым с потоковой передачей данных. Среди примеров подобного — фреймворки, которые динамически определяют, какие CSS-правила надо добавить на страницу в предшествующем сгенерированной разметке теге , или фреймворки, которые добавляют элементы в тег документа в процессе рендеринга тела документа. Если вы используете подобные фреймворки, вам, вероятно, придётся применять обычный рендеринг.

    Ещё один шаблон, который ещё не работает в React 16 — это встроенные вызовы renderToNodeStream в деревьях компонента. Обычное дело в React 15 — использовать renderToStaticMarkup для создания шаблона страницы и встраивать вызовы renderToString для формирования динамического содержимого. Например, это может выглядеть так:

    Однако, если заменить эти вызовы подсистемы рендеринга на их потоковые аналоги, код перестанет работать. Потки Readable (которые возвращаются из renderToNodeStream ) пока невозможно встраивать в компоненты как элементы. Надеюсь, такая возможность ещё будет добавлена в React.

    Итоги

    Итак, выше мы рассмотрели основные новшества серверного рендеринга в React 16. Надеюсь, вам они понравились так же, как и мне. В заключение хочу сказать огромное спасибо всем, кто участвовал в разработке React 16.

    Продолжаете читать? Вообще-то, пора бы уже с этим завязывать и попробовать что-нибудь отрендерить.

    Уважаемые читатели! Вы ещё здесь? Похоже, серверный рендеринг в React 16 вы уже испытали. Если так — просим поделиться впечатлениями.

    Server Rendering

    The most common use case for server-side rendering is to handle the initial render when a user (or search engine crawler) first requests our app. When the server receives the request, it renders the required component(s) into an HTML string, and then sends it as a response to the client. From that point on, the client takes over rendering duties.

    We will use React in the examples below, but the same techniques can be used with other view frameworks that can render on the server.

    Redux on the Server

    When using Redux with server rendering, we must also send the state of our app along in our response, so the client can use it as the initial state. This is important because, if we preload any data before generating the HTML, we want the client to also have access to this data. Otherwise, the markup generated on the client won’t match the server markup, and the client would have to load the data again.

    To send the data down to the client, we need to:

    • create a fresh, new Redux store instance on every request;
    • optionally dispatch some actions;
    • pull the state out of store;
    • and then pass the state along to the client.

    On the client side, a new Redux store will be created and initialized with the state provided from the server.
    Redux’s only job on the server side is to provide the initial state of our app.

    Setting Up

    In the following recipe, we are going to look at how to set up server-side rendering. We’ll use the simplistic Counter app as a guide and show how the server can render state ahead of time based on the request.

    Install Packages

    For this example, we’ll be using Express as a simple web server. We also need to install the React bindings for Redux, since they are not included in Redux by default.

    The Server Side

    The following is the outline for what our server side is going to look like. We are going to set up an Express middleware using app.use to handle all requests that come in to our server. If you’re unfamiliar with Express or middleware, just know that our handleRender function will be called every time the server receives a request.

    Additionally, as we are using ES6 and JSX syntax, we will need to compile with Babel (see this example of a Node Server with Babel) and the React preset.

    server.js

    Handling the Request

    The first thing that we need to do on every request is create a new Redux store instance. The only purpose of this store instance is to provide the initial state of our application.

    When rendering, we will wrap App /> , our root component, inside a Provider > to make the store available to all components in the component tree, as we saw in Usage with React.

    The key step in server side rendering is to render the initial HTML of our component before we send it to the client side. To do this, we use ReactDOMServer.renderToString().

    We then get the initial state from our Redux store using store.getState() . We will see how this is passed along in our renderFullPage function.

    Inject Initial Component HTML and State

    The final step on the server side is to inject our initial component HTML and initial state into a template to be rendered on the client side. To pass along the state, we add a script > tag that will attach preloadedState to window.__PRELOADED_STATE__ .

    The preloadedState will then be available on the client side by accessing window.__PRELOADED_STATE__ .

    We also include our bundle file for the client-side application via a script tag. This is whatever output your bundling tool provides for your client entry point. It may be a static file or a URL to a hot reloading development server.

    The Client Side

    The client side is very straightforward. All we need to do is grab the initial state from window.__PRELOADED_STATE__ , and pass it to our createStore() function as the initial state.

    Let’s take a look at our new client file:

    client.js

    You can set up your build tool of choice (Webpack, Browserify, etc.) to compile a bundle file into static/bundle.js .

    When the page loads, the bundle file will be started up and ReactDOM.render() will hook into the data-react-id attributes from the server-rendered HTML. This will connect our newly-started React instance to the virtual DOM used on the server. Since we have the same initial state for our Redux store and used the same code for all our view components, the result will be the same real DOM.

    And that’s it! That is all we need to do to implement server side rendering.

    But the result is pretty vanilla. It essentially renders a static view from dynamic code. What we need to do next is build an initial state dynamically to allow that rendered view to be dynamic.

    Preparing the Initial State

    Because the client side executes ongoing code, it can start with an empty initial state and obtain any necessary state on demand and over time. On the server side, rendering is synchronous and we only get one shot to render our view. We need to be able to compile our initial state during the request, which will have to react to input and obtain external state (such as that from an API or database).

    Processing Request Parameters

    The only input for server side code is the request made when loading up a page in your app in your browser. You may choose to configure the server during its boot (such as when you are running in a development vs. production environment), but that configuration is static.

    The request contains information about the URL requested, including any query parameters, which will be useful when using something like React Router. It can also contain headers with inputs like cookies or authorization, or POST body data. Let’s see how we can set the initial counter state based on a query parameter.

    server.js

    The code reads from the Express Request object passed into our server m ll see the counter output as 100 and the __PRELOADED_STATE__ variable has the counter set in it.

    Async State Fetching

    The most common issue with server side rendering is dealing with state that comes in asynchronously. Rendering on the server is synchronous by nature, so it’s necessary to map any asynchronous fetches into a synchronous operation.

    The easiest way to do this is to pass through some callback back to your synchronous code. In this case, that will be a function that will reference the response object and send back our rendered HTML to the client. Don’t worry, it’s not as hard as it may sound.

    For our example, we’ll imagine there is an external datastore that contains the counter’s initial value (Counter As A Service, or CaaS). We’ll make a mock call over to them and build our initial state from the result. We’ll start by building out our API call:

    api/counter.js

    Again, this is just a mock API, so we use setTimeout to simulate a network request that takes 500 milliseconds to respond (this should be much faster with a real world API). We pass in a callback that returns a random number asynchronously. If you’re using a Promise-based API client, then you would issue this callback in your then handler.

    On the server side, we simply wrap our existing code in the fetchCounter and receive the result in the callback:

    server.js

    Because we call res.send() inside of the callback, the server will hold open the connection and won’t send any data until that callback executes. You’ll notice a 500ms delay is now added to each server request as a result of our new API call. A more advanced usage would handle errors in the API gracefully, such as a bad response or timeout.

    Security Considerations

    Because we have introduced more code that relies on user generated content (UGC) and input, we have increased our attack surface area for our application. It is important for any application that you ensure your input is properly sanitized to prevent things like cross-site scripting (XSS) attacks or code injections.

    In our example, we take a rudimentary approach to security. When we obtain the parameters from the request, we use parseInt on the counter parameter to ensure this value is a number. If we did not do this, you could easily get dangerous data into the rendered HTML by providing a script tag in the request. That might look like this: ?counter= script > script > doSomethingBad(); script >

    For our simplistic example, coercing our input into a number is sufficiently secure. If you’re handling more complex input, such as freeform text, then you should run that input through an appropriate sanitization function, such as validator.js.

    Furthermore, you can add additional layers of security by sanitizing your state output. JSON.stringify can be subject to script injections. To counter this, you can scrub the JSON string of HTML tags and other dangerous characters. This can be done with either a simple text replacement on the string, e.g. JSON.stringify(state).replace(/ , or via more sophisticated libraries such as serialize-javascript.

    Next Steps

    You may want to read Async Actions to learn more about expressing asynchronous flow in Redux with async primitives such as Promises and thunks. Keep in mind that anything you learn there can also be applied to universal rendering.

    If you use something like React Router, you might also want to express your data fetching dependencies as static fetchData() methods on your route handler components. They may return async actions, so that your handleRender function can match the route to the route handler component classes, dispatch fetchData() result for each of them, and render only after the Promises have resolved. This way the specific API calls required for different routes are colocated with the route handler component definitions. You can also use the same technique on the client side to prevent the router from switching the page until its data has been loaded.

    Стратегии для серверной рендеринга асинхронно инициализированных компонентов React.js

    Одно из самых больших преимуществ React.jsрендеринг на стороне сервера. Проблема в том, что ключевая функция React.renderComponentToString() является синхронной, что делает невозможным загрузку любых асинхронных данных по мере рендеринга иерархии компонентов на сервере.

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

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

    Вопрос заключается в том, что лучший способ заполнить мой магазин. В прошлые дни я много гулял, и я столкнулся с несколькими стратегиями, ни один из которых не казался действительно хорошим, учитывая, насколько эта особенность React «продвигается».

    По-моему, самый простой способ — заполнить все мои магазины до начала фактического рендеринга. Это означает, что где-то вне иерархии компонентов (например, подключен к моему маршрутизатору). Проблема с этим подходом заключается в том, что мне придется в значительной степени определить структуру страницы дважды. Рассмотрим более сложную страницу, например, страницу блога со множеством разных компонентов (фактическое сообщение в блоге, комментарии, связанные сообщения, новые сообщения, твиттер-поток. ). Мне пришлось бы разрабатывать структуру страницы с использованием компонентов React, а затем в другом месте мне нужно было бы определить процесс заполнения каждого требуемого хранилища для этой текущей страницы. Это не похоже на хорошее решение для меня. К сожалению, большинство изоморфных учебников разработаны таким образом (например, этот отличный flux-tutorial).

    React-async. Этот подход идеален. Это позволяет мне просто определить в специальной функции в каждом компоненте инициализацию состояния (неважно, синхронно или асинхронно), и эти функции вызывается, когда иерархия отображается в HTML. Он работает таким образом, что компонент не отображается, пока состояние полностью не инициализируется. Проблема в том, что она требует Fibers, которая, насколько я понимаю, является расширением Node.js, которое изменяет стандартное поведение JavaScript. Хотя мне действительно нравится результат, мне все же кажется, что вместо того, чтобы найти решение, мы изменили правила игры. И я думаю, мы не должны быть вынуждены сделать это, чтобы использовать эту основную функцию React.js. Я также не уверен в общей поддержке этого решения. Можно ли использовать Fiber на стандартном веб-хостинге Node.js?

    Я думал немного о себе. Я действительно не думал о деталях реализации, но общая идея заключается в том, что я бы расширил компоненты аналогично React-async, а затем я бы повторно назовет React.renderComponentToString() в корневом компоненте. Во время каждого прохода я собирал расширившиеся обратные вызовы, а затем вызывал их на проходе и в проходе, чтобы заполнить магазины. Я бы повторил этот шаг, пока не будут заполнены все магазины, требуемые текущей иерархией компонентов. Есть много вещей, которые нужно решить, и я особенно не уверен в производительности.

    Я что-то пропустил? Есть ли другой подход/решение? Прямо сейчас я думаю о том, как идти по пути реакции-асинк/волокна, но я не совсем уверен в этом, как объясняется во втором.

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

    Информационный портал по безопасности

    Информационный портал по безопасности » Программирование » Веб-разработка » Create React App (aka React Scripts) и серверный рендеринг с Redux и Router

    Create React App (aka React Scripts) и серверный рендеринг с Redux и Router

    Из комментариев к статье стало понятно, что очень многие люди склоняются в сторону экосистемы Create React App (он же React Scripts). Это вполне разумно, т.к. это самый популярный и простой в использовании продукт (благодаря отсутствию конфигурации и поддержке ведущих людей React-сообщества), в котором, к тому же, есть почти все необходимое — сборка, режим разработки, тесты, статистика покрытия. Не хватает только серверного рендеринга.

    В качестве одного из способов в официальной документации предлагается либо вбивать начальные данные в шаблон либо воспользоваться статическими слепками . Первый подход не позволит поисковикам нормально индексировать статичный HTML, а второй — не поддерживает проброс никаких начальных данных, кроме HTML ( фраза из документации : this doesn’t pass down any state except what’s contained in the markup). Поэтому если используется Redux, то придется для рендеринга использовать что-то другое.

    Я адаптировал пример из статьи для использования с Create React App, теперь он называется Create React Server и умеет запускать серверный рендеринг командой:

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

    Небольшое лирическое отступление. Как говорят авторы React Router — их сайты индексируются Гуглом без проблем и без всякого серверного рендеринга. Может это и так. Но одной из главных проблем является не только Гугл, но и быстрая доставка контента юзеру, и это может даже поважнее индексации, которую можно обмануть.

    Установка

    Для начала установим требующийся для этого примера пакеты:

    Добавим файл .babelrc или секцию babel в файл package.json

    Пресет babel-preset-react-app ставится вместе с react-scripts , но для серверного рендеринга нам надо явно на него сослаться.

    Страница (т.е. конечная точка React Router)

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

    Сервер берет конечный компонент, вызывает у него getInitialProps , внутри которого можно сделать диспатч экшнов Redux’а и вернуть начальный набор props (на случай, если Redux не используется). Метод вызывается как на клиенте, так и на сервере, что позволяет сильно упростить начальную загрузку данных.

    Переменная initialError будет иметь значение, если в функции getInitialProps возникла ошибка, причем не важно где — на клиенте или на сервере, поведение одинаково.

    Страница, которая будет использоваться как заглушка для 404 ошибок должна иметь статическое свойство notFound :

    Router

    Функция createRoutes должна возвращать правила роутера, асинхронные роуты тоже поддерживаются, но для простоты это пока опустим:

    Redux

    Функция createStore должна принимать начальное состояние в качестве параметра и возвращать новый Store :

    Когда функция вызывается на сервере, второй параметр будет иметь объекты Request и Response из NodeJS, можно вытащить некую информацию и вложить ее в начальное состояние.

    Главная входная точка

    Соберем все воедино, а также добавим специальную обертку для получения initialProps с сервера:

    Запуск простого сервера через консольную утилиту

    Добавим скрипты в секцию scripts файла package.json :

    Теперь если мы откроем http://localhost:3000 в браузере — мы увидим страницу, подготовленную на сервере.

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

    Запуск сервера через API и сохранение результатов сборки

    Если возможностей командной строки стало мало, или требуется хранить результаты сборки сервера, то всегда можно создать сервер не через CLI, а через API.

    Установим в дополнение к предыдущим пакетам babel-cli , он понадобится для сборки сервера:

    Добавим скрипты в секцию scripts файла package.json :

    Таким образом клиентская часть будет по-прежнему собираться Create React App (React Scripts), а серверная — с помощью Babel, который заберет все и src и положит в build-lib .

    Добавить комментарий