Изучение методов разделения кода с помощью Webpack и RxJS
Рассуждение
Скорость. Одна из самых важных частей любого веб-сайта - это время интерактивности.
Разделив наше приложение на блоки, мы можем загружать и оценивать только тот код, который требуется для отображаемой страницы, что позволяет нам сохранять короткое время загрузки по мере того, как общее количество страниц в нашем приложении растет и становится более функциональным. богатый.
Предположения
В следующем посте предполагается, что читатель уже имел опыт работы с React / JSX, React Router и Webpack.
Если вы не знакомы с упомянутыми выше библиотеками, я включил несколько полезных ресурсов для начала работы в нижней части этого сообщения для каждой библиотеки соответственно.
Техника
Для этой демонстрации мы будем использовать следующие зависимости с открытым исходным кодом:
- Webpack 2 (хотя Webpack 1.x можно использовать, рекомендуется выполнить миграцию)
- React Router 4 beta-6
- RxJS 5
- React 15.x
- Babel 6 с различными плагинами и пресетами
Завершенный пример проекта с живой демонстрацией для этого сообщения можно найти по адресу: https://github.com/luigiplr/react-router-redux-rxjs-code-splitting
Начиная
К счастью для нас, в Webpack уже встроено разделение кода!
Мы можем использовать его, определяя точки останова разделения с помощью оператора ES6import
в нашем приложении следующим образом:
const page1 = () => import('./page1.jsx') page1().then(loadedModule => { console.log(loadedModule) })
Примечание. Если вы используете синтаксис export
модуля ES6, вам нужно будет изменить приведенный выше код, чтобы использовать свойство loadedModule.default
только что загруженного модуля. Код в примере проекта содержит для этого вспомогательные функции.
Вот и все. Теперь Webpack сделает все остальное разумно, любой код, на который имеется прямая ссылка из page1.jsx
, теперь разделен на собственный кусок.
Пример того, как реализовать эту логику в маршруте React Router 4, выглядит следующим образом:
Выше мы определили HomeRoute
как новый asyncRoute()
и передали компонент, который мы хотим загрузить, как функцию, которая возвращает наш модуль как блок Webpack Promise.
Примечание. Способность Webpack разделять код на разные части полагается на статический анализ кода ваших приложений во время сборки, поэтому рекомендуется полностью определять импорт перед запуском, а не используйте строки шаблона для импорта фрагментов.
Хотя на первый взгляд создание нескольких asyncRoute()
может показаться легко абстрагированным вокруг более параметрического замедления, это потенциально может иметь непреднамеренные побочные эффекты (например, вспомогательные файлы также разделяются). Пример того, что происходит, когда Webpack анализирует литералы шаблона и статический импорт, можно увидеть ниже.
/* every "importable" file within "pages/" is split into its own respective chunk. T
his can have adverse effects if our directory contains helper files etc. */ const parametricRuntimeImport = name =>import(`./pages/${name}`)
/* only intended files are split, no surprises. */ const staticImport = name => { switch (name) { case 'page1': return import('./pages/page1') case 'page2: return import('./pages/page2') } }
Здесь мы создаем класс AsyncRoute за asyncRoute()
. Это гарантирует, что каждый маршрут имеет свой собственный экземпляр AsyncRoute и позволяет нам передавать в качестве аргумента создателя Promise загрузчика фрагментов.
Когда компонент монтируется, он загружается в наш фрагмент кода, разделенный на наш аргумент getComponent
. После загрузки мы устанавливаем наш статический параметр (поэтому, если мы вернемся к этому маршруту, мы не будем пытаться повторно загрузить существующий кусок) и обновим состояние нашего компонента с помощью нашего блестящего нового компонента.
Примечание. Этот класс не следует использовать для рендеринга на стороне сервера; О том, как реализовать рендеринг на стороне сервера в приложении, написанном на основе кода, будет рассказано в следующей публикации.
Загадка Redux
Redux снова делает управление состоянием нашего приложения интересным.
Неудивительно, что для работы Redux все редукторы должны быть загружены, прежде чем они смогут выполнять какие-либо действия, нацеленные на них. Поскольку мы хотим кодировать наши редукторы по принципу «маршрут за маршрутом», мы должны создать надежный способ их внедрения; Здесь в игру вступает наш реестр Redux.
При использовании нашего реестра Redux наша стандартная функция createStore()
должна быть изменена соответствующим образом.
Вместо того, чтобы комбинировать наши начальные редукторы, мы передаем их как объект в конструктор реестра как baseReducers
.
В нашей демонстрации начальные редукторы состоят из одного фиктивного редуктора с пустым объектом в качестве состояния по умолчанию.
В дополнение к извлечению наших исходных комбинированных редюсеров из реестра мы должны назначить созданное хранилище нашему реестру, назначив его registry.store
, чтобы мы могли взаимодействовать с методами api хранилища Redux для замены редукторов и т. Д.
Выше мы видим, что наш injectReducers()
метод объединяет новые редукторы с нашими текущими редукторами. Хотя этот метод не имеет защиты от двойного внедрения редуктора, его можно добавить, исключив любые повторяющиеся ключи в наших новых редукторах, которые уже существуют в нашем текущем состоянии. Двойное введение редуктора приведет к ошибке Redux.
Примечание: наш реестр ограничен внедрением только новых редукторов; Функциональность может быть расширена за счет включения, среди прочего, внедрения Redux Observable Epics и SSR Epics / Reducer регидратации / начальной загрузки.
Позволяет нам отправлять инъекции редуктора - это наше настраиваемое промежуточное программное обеспечение, которое использует любые допустимые STORE_INJECT
действия; Мы используем символ, чтобы гарантировать, что мы никогда не пропустим отправку без инъекции.
Мы используем настраиваемое действие для передачи сообщений нашему промежуточному программному обеспечению Redux; Использование имен вычисляемых свойств ES6 упрощает задачу.
Предостережение для нашего метода реестра заключается в том, что каждый «инъекционный» редуктор должен иметь reducer
ключ, назначенный его прототипу с желаемым именем редуктора в состоянии Redux.
Почему RxJS
Думайте о RxJS как о Lodash для событий. - Введение в RxJS
RxJS предоставляет нам отличный способ создания событийно-управляемых отменяемых Observables, он очень хорошо подходит, поскольку навигация по страницам также по своей сути управляется событиями.
Его полезность для нас можно резюмировать в следующих сценариях:
Сценарий 1 (без использования RxJS)
- Пользователь попадает на наш сайт
- Пользователь переключается на вкладку 1, и AsyncRoute начинает загрузку в компоненте и редукторе маршрута соответственно.
- Пользователь решает, что он предпочел бы перейти на вкладку 2, и быстро переключается
- AsyncRoute вкладки 2 теперь начинает загружать все свои компоненты и редукторы соответственно.
- Вкладка 1 заканчивается; начинает вводить свои редукторы
- К сожалению, для нас наш Пользователь находится на мобильном устройстве, и теперь мы вводим два редуктора при монтировании компонента React; Наш сайт стал заметно вялым, и мы попрощались с любой плавностью переходов между маршрутами.
Сценарий 2 (с использованием RxJS)
- Пользователь попадает на наш сайт
- Пользователь переключается на вкладку 1, и AsyncRoute начинает загрузку в компоненте и редукторе маршрута соответственно.
- Пользователь решает, что они предпочли бы перейти на вкладку 2, и быстро переключается, вызывается метод
componentWillUnmount()
вкладки 1, и наши подписки на загрузчик фрагментов отменяются. - AsyncRoute вкладки 2 начинает загружать все свои компоненты и редукторы соответственно.
- Поскольку редукторы Tab 1 никогда не вводились; Наше приложение работает, как ожидалось, без заметных замедлений
Собираем все вместе
Теперь, когда у нас есть возможность загружать и вставлять фрагменты редуктора, мы можем изменить наш app.jsx
, чтобы он выглядел следующим образом:
Как и до того, как загрузчик компонентов Route был передан в качестве первого аргумента функции asyncRoute()
, теперь у нас есть необязательный второй аргумент, который принимает загрузчик редуктора.
Примечание. Если вам требуется больше редукторов для каждого маршрута, вы можете объединить свои import()
в Promise.all()
, поскольку они изначально являются собственными обещаниями.
Статистика
Пакет до:
Связать после:
Примечание. Для достижения наилучших результатов используйте в более крупных приложениях. 😛
Живая демонстрация: https://luigiplr.github.io/react-router-redux-rxjs-code-splitting
TL;DR;
- RxJS упрощает загрузку чанка, вызванную пользователем, проблемы, которые привели к взломам для отмены обещаний, больше не нужны.
- React Router v4 - это круто; Попробуйте!
- Стоит перейти на Webpack 2
- Код разбивает все на части
Обратная связь?
- Я где-то облажался?
- Есть лучшее представление о том, как что-то сделать?
Я хотел бы услышать / узнать об этом; Отправить выпуск / PR!
Полезные ресурсы
Webpack:
React / JSX
- Hello World (официальная документация React)
- Начало работы с React и JSX