Ленивая загрузка маршрутов с Vue Router

Введение По умолчанию при написании одностраничного приложения (SPA) Vue.js все необходимые ресурсы, такие как файлы JavaScript и CSS, загружаются вместе при загрузке страницы. При работе с большими файлами это может привести к неудовлетворительному взаимодействию с пользователем. С помощью Webpack можно загружать страницы по запросу в Vue.js, используя функцию import () вместо ключевого слова import. Зачем загружать по запросу? Типичный SPA в Vue.js работает за счет того, что все функции и ресурсы упакованы в

Вступление

По умолчанию при написании одностраничного приложения (SPA) Vue.js все необходимые ресурсы, такие как файлы JavaScript и CSS, загружаются вместе при загрузке страницы. При работе с большими файлами это может привести к неудовлетворительному взаимодействию с пользователем.

С помощью Webpack можно загружать страницы по запросу в Vue.js, используя функцию import() вместо ключевого слова import .

Зачем загружать по запросу?

Типичный SPA в Vue.js работает за счет того, что все функциональные возможности и ресурсы упаковываются и доставляются вместе, чтобы пользователи могли использовать приложение без необходимости обновлять страницы. Если вы явно не разработали приложение для загрузки страниц по запросу, все страницы будут загружены либо сразу, либо предварительно загружены / загружены заранее, с использованием ненужной полосы пропускания и замедлением загрузки страницы.

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

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

Подготовка проекта

Во-первых, нам нужен способ взаимодействия нашего индикатора выполнения с Vue Router. Для этого мы будем использовать шаблон шины событий .

Шина событий - это, по сути, одноэлементный экземпляр Vue. Поскольку все экземпляры Vue имеют систему событий с использованием $on и $emit , мы можем использовать ее для передачи событий в любом месте нашего приложения.

Создадим новый файл eventHub.js в каталоге components

 import Vue from 'vue' 
 export default new Vue() 

Теперь мы настроим Webpack, чтобы отключить предварительную выборку и предварительную загрузку. Мы можем сделать это индивидуально для каждой функции или отключить глобально. Создайте vue.config.js в корневой папке и добавьте конфигурацию для отключения предварительной выборки и предварительной загрузки:

 module.exports = { 
 chainWebpack: (config) => { 
 // Disable prefetching and preloading 
 config.plugins.delete('prefetch') 
 config.plugins.delete('preload') 
 }, 
 } 

Добавление маршрутов и страниц

Мы будем использовать Vue Router. Для этого мы будем использовать npx для его установки:

 $ npx vue add router 

Теперь давайте отредактируем наш файл маршрутизатора, обычно расположенный в router/index.js и обновим наши маршруты, чтобы использовать функцию import() вместо оператора import

Это конфигурация по умолчанию:

 import About from '../views/About.vue' 
 { 
 path: '/about', 
 name: 'About', 
 component: About 
 }, 

Мы изменили его на:

 { 
 path: '/about', 
 name: 'About', 
 component: () => import('../views/About.vue') 
 }, 

Если вы предпочитаете выбирать, какие страницы загружать по запросу, а не отключать предварительную выборку и предварительную загрузку глобально, используйте специальные комментарии Webpack вместо настройки Webpack в vue.config.js :

 import( 
 /* webpackPrefetch: true */ 
 /* webpackPreload: true */ 
 '../views/About.vue' 
 ) 

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

Реализация индикатора выполнения

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

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

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

 $ npm i lodash.random 

Затем создадим компонент Vue - components/ProgressBar.vue :

 <template> 
 <div :class="{'loading-container': true, loading: isLoading, visible: isVisible}"> 
 <div class="loader" :style="{ width: progress + '%' }"> 
 <div class="light"></div> 
 </div> 
 <div class="glow"></div> 
 </div> 
 </template> 

Теперь к этому компоненту мы добавим скрипт. В этом скрипте мы сначала импортируем random и $eventHub , так как мы будем их использовать:

 <script> 
 import random from 'lodash.random' 
 import $eventHub from '../components/eventHub' 
 </script> 

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

 // Assume that loading will complete under this amount of time. 
 const defaultDuration = 8000 
 // How frequently to update 
 const defaultInterval = 1000 
 // 0 - 1. Add some variation to how much the bar will grow at each interval 
 const variation = 0.5 
 // 0 - 100. Where the progress bar should start from. 
 const startingPoint = 0 
 // Limiting how far the progress bar will get to before loading is complete 
 const endingPoint = 90 

Имея это на месте, давайте напишем логику для асинхронной загрузки компонентов:

 export default { 
 name: 'ProgressBar', 
 
 data: () => ({ 
 isLoading: true, // Once loading is done, start fading away 
 isVisible: false, // Once animate finish, set display: none 
 progress: startingPoint, 
 timeoutId: undefined, 
 }), 
 
 mounted() { 
 $eventHub.$on('asyncComponentLoading', this.start) 
 $eventHub.$on('asyncComponentLoaded', this.stop) 
 }, 
 
 methods: { 
 start() { 
 this.isLoading = true 
 this.isVisible = true 
 this.progress = startingPoint 
 this.loop() 
 }, 
 
 loop() { 
 if (this.timeoutId) { 
 clearTimeout(this.timeoutId) 
 } 
 if (this.progress >= endingPoint) { 
 return 
 } 
 const size = (endingPoint - startingPoint) / (defaultDuration / defaultInterval) 
 const p = Math.round(this.progress + random(size * (1 - variation), size * (1 + variation))) 
 this.progress = Math.min(p, endingPoint) 
 this.timeoutId = setTimeout( 
 this.loop, 
 random(defaultInterval * (1 - variation), defaultInterval * (1 + variation)) 
 ) 
 }, 
 
 stop() { 
 this.isLoading = false 
 this.progress = 100 
 clearTimeout(this.timeoutId) 
 const self = this 
 setTimeout(() => { 
 if (!self.isLoading) { 
 self.isVisible = false 
 } 
 }, 200) 
 }, 
 }, 
 } 

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

И, наконец, добавим к нему немного стиля:

 <style scoped> 
 .loading-container { 
 font-size: 0; /* remove space */ 
 position: fixed; 
 top: 0; 
 left: 0; 
 height: 5px; 
 width: 100%; 
 opacity: 0; 
 display: none; 
 z-index: 100; 
 transition: opacity 200; 
 } 
 
 .loading-container.visible { 
 display: block; 
 } 
 .loading-container.loading { 
 opacity: 1; 
 } 
 
 .loader { 
 background: #23d6d6; 
 display: inline-block; 
 height: 100%; 
 width: 50%; 
 overflow: hidden; 
 border-radius: 0 0 5px 0; 
 transition: 200 width ease-out; 
 } 
 
 .loader > .light { 
 float: right; 
 height: 100%; 
 width: 20%; 
 background-image: linear-gradient(to right, #23d6d6, #29ffff, #23d6d6); 
 animation: loading-animation 2s ease-in infinite; 
 } 
 
 .glow { 
 display: inline-block; 
 height: 100%; 
 width: 30px; 
 margin-left: -30px; 
 border-radius: 0 0 5px 0; 
 box-shadow: 0 0 10px #23d6d6; 
 } 
 
 @keyframes loading-animation { 
 0% { 
 margin-right: 100%; 
 } 
 50% { 
 margin-right: 100%; 
 } 
 100% { 
 margin-right: -10%; 
 } 
 } 
 </style> 

Теперь давайте добавим наш ProgressBar в наш App.vue или компонент макета, если он находится в том же компоненте, что и представление маршрутизатора. Мы хотим, чтобы он был доступен на протяжении всего жизненного цикла приложения:

 <template> 
 <div> 
 <progress-bar></progress-bar> 
 <router-view></router-view> 
 <!--- your other components --> 
 </div> 
 </template> 
 
 <script> 
 import ProgressBar from './components/ProgressBar.vue' 
 export default { 
 components: { ProgressBar }, 
 } 
 </script> 

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

индикатор{.ezlazyload}

Индикатор выполнения запуска для страниц с отложенной загрузкой

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

 import $eventHub from '../components/eventHub' 
 
 router.beforeEach((to, from, next) => { 
 if (typeof to.matched[0]?.components.default === 'function') { 
 $eventHub.$emit('asyncComponentLoading', to) // Start progress bar 
 } 
 next() 
 }) 
 
 router.beforeResolve((to, from, next) => { 
 $eventHub.$emit('asyncComponentLoaded') // Stop progress bar 
 next() 
 }) 

Чтобы определить, загружена ли страница лениво, нам нужно проверить, определен ли компонент как динамический импорт, то есть component: () => import('...') вместо component: MyComponent .

Это делается с помощью typeof to.matched[0]?.components.default === 'function' . Компоненты, которые были загружены с помощью import , не будут классифицироваться как функции.

Заключение

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

comments powered by Disqus