Статьи

Оптимизация производительности приложения Vue с помощью асинхронных компонентов

Хотите узнать Vue.js с нуля? Получите полную коллекцию книг Vue, охватывающих основы, проекты, советы и инструменты и многое другое с SitePoint Premium. Присоединяйтесь сейчас всего за $ 14,99 / месяц .

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

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

Чтобы следовать этому руководству, вам необходимо базовое понимание Vue.js и, необязательно, Node.js.

Асинхронные компоненты

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

<!-- Message.vue --> <template> <h1>New message!</h1> </template> 

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

 <!-- App.vue --> <template> <div> <message></message> </div> </template> <script> import Message from "./Message"; export default { components: { Message } }; </script> 

Но что происходит сейчас? Компонент Message будет загружаться при каждой загрузке приложения, поэтому он включается в первоначальную загрузку.

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

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

Ленивая загрузка — все о задержке начальной загрузки компонента. Вы можете увидеть ленивую загрузку в действии на таких сайтах, как medium.com, где изображения загружаются непосредственно перед тем, как они требуются. Это полезно, поскольку нам не нужно тратить ресурсы на загрузку всех изображений для определенного поста заранее, поскольку читатель может пропустить статью наполовину.

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

Динамический импорт

К счастью, Vue обслуживает этот сценарий, используя так называемый динамический импорт . Эта функция представляет новую функциональную форму импорта, которая возвращает Promise, содержащий запрошенный (Vue) компонент. Поскольку импорт — это функция, получающая строку, мы можем выполнять мощные операции, такие как загрузка модулей с использованием выражений. Динамический импорт доступен в Chrome начиная с версии 61. Дополнительную информацию о них можно найти на веб-сайте разработчиков Google .

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

 // static import import Message from "./Message"; // dynamic import import("./Message").then(Message => { // Message module is available here... }); 

Теперь давайте применим эти знания к нашему компоненту Message , и мы получим компонент App.vue который выглядит следующим образом:

 <!-- App.vue --> <template> <div> <message></message> </div> </template> <script> import Message from "./Message"; export default { components: { Message: () => import("./Message") } }; </script> 

Как видите, функция import() разрешает Promise, который возвращает компонент, что означает, что мы успешно загрузили наш компонент асинхронно. Если вы посмотрите на вкладку сети вашего devtools, вы увидите файл с именем 0.js который содержит ваш асинхронный компонент.

Code splitting webpack

Условная загрузка асинхронных компонентов

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

Настройка проекта

Если у вас не установлен Vue CLI, вам нужно взять его сейчас:

 npm i -g @vue/cli 

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

 vue create my-store 

Перейдите в каталог проекта, затем установите библиотеку ant-design-vue , которую мы будем использовать для стилизации:

 cd my-store npm i ant-design-vue 

Затем импортируйте библиотеку Ant Design в src/main.js :

 import 'ant-design-vue/dist/antd.css' 

Наконец, создайте два новых компонента в src/comonents , Checkout.vue и Items.vue :

 touch src/components/{Checkout.vue,Items.vue} 

Делаем представление магазина

Откройте src/App.vue и замените код следующим:

 <template> <div id="app"> <h1>{{ msg }}</h1> <items></items> </div> </template> <script> import items from "./components/Items" export default { components: { items }, name: 'app', data () { return { msg: 'My Fancy T-Shirt Store' } } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } h1, h2 { font-weight: normal; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style> 

Здесь нет ничего необычного. Все, что мы делаем, это отображаем сообщение и отображаем компонент <items> .

Затем откройте src/components/Items.vue и добавьте следующий код:

 <template> <div> <div style="padding: 20px;"> <Row :gutter="16"> <Col :span="24" style="padding:5px"> <Icon type="shopping-cart" style="margin-right:5px"/>{{shoppingList.length}} item(s) <Button @click="show = true" id="checkout">Checkout</Button> </Col> </Row> </div> <div v-if="show"> <Row :gutter="16" style="margin:0 400px 50px 400px"> <checkout v-bind:shoppingList="shoppingList"></checkout> </Row> </div> <div style="background-color: #ececec; padding: 20px;"> <Row :gutter="16"> <Col :span="6" v-for="(item, key) in items" v-bind:key="key" style="padding:5px"> <Card v-bind:title="item.msg" v-bind:key="key"> <Button type="primary" @click="addItem(key)">Buy ${{item.price}}</Button> </Card> </Col> </Row> </div> </div> </template> <script> import { Card, Col, Row, Button, Icon } from 'ant-design-vue'; export default { methods: { addItem (key) { if(!this.shoppingList.includes(key)) { this.shoppingList.push(key); } } }, components: { Card, Col, Row, Button, Icon, checkout: () => import('./Checkout') }, data: () => ({ items: [ { msg: 'First Product', price: 9.99 }, { msg: 'Second Product', price: 19.99 }, { msg: 'Third Product', price: 15.00 }, { msg: 'Fancy Shirt', price: 137.00 }, { msg: 'More Fancy', price: 109.99 }, { msg: 'Extreme', price: 3.00 }, { msg: 'Super Shirt', price: 109.99 }, { msg: 'Epic Shirt', price: 3.00 }, ], shoppingList: [], show: false }) } </script> <style> #checkout { background-color:#e55242; color:white; margin-left: 10px; } </style> 

В этом файле мы показываем значок корзины с текущим количеством купленных товаров. Сами элементы извлекаются из массива items , объявленного как свойство данных. Если вы щелкнете по кнопке Buy элемента, addItem метод addItem , который addItem данный элемент в массив shoppingList . В свою очередь, это увеличит общую сумму корзины.

Мы также добавили кнопку « Оформить заказ» на страницу, и здесь все становится интересным:

 <Button @click="show = true" id="checkout">Checkout</Button> 

Когда пользователь нажимает на эту кнопку, мы устанавливаем параметр show как true . Это true значение очень важно для условной загрузки нашего асинхронного компонента.

Несколько строк ниже вы можете найти оператор v-if , который отображает содержимое <div> если для show задано значение true . Этот <div> содержит компонент оформления заказа, который мы хотим загрузить только тогда, когда пользователь нажал кнопку извлечения.

Компонент извлечения загружается асинхронно в параметре components в разделе <script> . Крутая вещь в том, что мы можем даже передавать аргументы компоненту с помощью оператора v-bind . Как видите, создать условные асинхронные компоненты довольно просто:

 <div v-if="show"> <checkout v-bind:shoppingList="shoppingList"></checkout> </div> 

Давайте быстро добавим код для компонента Checkout в src/components/Checkout.vue :

 <template> <Card title="Checkout Items" key="checkout"> <p v-for="(k, i) in this.shoppingList" :key="i"> Item: {{items[Number(k)].msg}} for ${{items[Number(k)].price}} </p> </Card> </template> <script> import { Card } from 'ant-design-vue'; export default { props: ['shoppingList'], components: { Card }, data: () => ({ items: [ { msg: 'First Product', price: 9.99 }, { msg: 'Second Product', price: 19.99 }, { msg: 'Third Product', price: 15.00 }, { msg: 'Fancy Shirt', price: 137.00 }, { msg: 'More Fancy', price: 109.99 }, { msg: 'Extreme', price: 3.00 }, { msg: 'Super Shirt', price: 109.99 }, { msg: 'Epic Shirt', price: 3.00 }, ] }) } </script> 

Здесь мы перебираем реквизиты, которые мы получаем в виде shoppingList и выводим их на экран.

Вы можете запустить приложение с помощью команды npm run serve . Затем перейдите по адресу http: // localhost: 8080 / . Если все прошло по плану, вы должны увидеть что-то вроде того, что показано на рисунке ниже.

Vue Webshop Demo

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

Вы также можете найти код для этой демонстрации на GitHub .

Асинхронизация с загрузкой и ошибкой компонента

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

 const Message = () => ({ component: import("./Message"), loading: LoadingAnimation, error: ErrorComponent }); 

Вывод

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