[Перевод] Разработка более быстрых приложений на Vue.js
JavaScript — это душа современных веб-приложений. Это — главный ингредиент фронтенд-разработки. Существуют различные JavaScript-фреймворки для создания интерфейсов веб-проектов. Vue.js — это один из таких фреймворков, который можно отнести к довольно популярным решениям.
Vue.js — это прогрессивный фреймворк, предназначенный для создания пользовательских интерфейсов. Его базовая библиотека направлена, в основном, на создание видимой части интерфейсов. В проект, основанный на Vue, при необходимости легко интегрировать и другие библиотеки. Кроме того, с помощью Vue.js и с привлечением современных инструментов и вспомогательных библиотек, можно создавать сложные одностраничные приложения.

В этом материале будет описан процесс создания простого Vue.js-приложения, предназначенного для работы с заметками о неких задачах. Вот репозиторий фронтенда проекта. Вот — репозиторий его бэкенда. Мы, по ходу дела, разберём некоторые мощные возможности Vue.js и вспомогательных инструментов.
Создание проекта
Прежде чем мы перейдём к разработке — давайте создадим и настроим базовый проект нашего приложения по управлению задачами.
- Создадим новый проект, воспользовавшись интерфейсом командной строки Vue.js 3:
vue create notes-app - Добавим в проект файл
package.jsonследующего содержания:{ "name": "notes-app", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "axios": "^0.19.1", "buefy": "^0.8.9", "core-js": "^3.4.4", "lodash": "^4.17.15", "marked": "^0.8.0", "vee-validate": "^3.2.1", "vue": "^2.6.10", "vue-router": "^3.1.3" }, "devDependencies": { "@vue/cli-plugin-babel": "^4.1.0", "@vue/cli-plugin-eslint": "^4.1.0", "@vue/cli-service": "^4.1.0", "@vue/eslint-config-prettier": "^5.0.0", "babel-eslint": "^10.0.3", "eslint": "^5.16.0", "eslint-plugin-prettier": "^3.1.1", "eslint-plugin-vue": "^5.0.0", "prettier": "^1.19.1", "vue-template-compiler": "^2.6.10" } } - Установим зависимости, описанные в
package.json:npm install
Теперь, после того, как база приложения готова, мы можем переходить к следующему шагу работы над ним.
Маршрутизация
Маршрутизация (routing) — это одна из замечательных возможностей современных веб-приложений. Маршрутизатор можно интегрировать в Vue.js-приложение, воспользовавшись библиотекой vue-router. Это — официальный маршрутизатор для Vue.js-проектов. Среди его возможностей отметим следующие:
- Вложенные маршруты/представления.
- Модульная конфигурация маршрутизатора.
- Доступ к параметрам маршрута, запросам, шаблонам.
- Анимация переходов представлений на основе возможностей Vue.js.
- Удобный контроль навигации.
- Поддержка автоматической стилизации активных ссылок.
- Поддержка HTML5-API history, возможность использования URL-хэшей, автоматическое переключение в режим совместимости с IE9.
- Настраиваемое поведение прокрутки страницы.
Для реализации маршрутизации в нашем приложении создадим, в папке router, файл index.js. Добавим в него следующий код:
import Vue from "vue";
import VueRouter from "vue-router";
import DashboardLayout from "../layout/DashboardLayout.vue";
Vue.use(VueRouter);
const routes = [
{
path: "/home",
component: DashboardLayout,
children: [
{
path: "/notes",
name: "Notes",
component: () =>
import(/* webpackChunkName: "home" */ "../views/Home.vue")
}
]
},
{
path: "/",
redirect: { name: "Notes" }
}
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
export default router;
Рассмотрим объект routes, который включает в себя описание маршрутов, поддерживаемых приложением. Здесь используются вложенные маршруты.
Объект children содержит вложенные маршруты, которые будут показаны на странице приложения, представляющей его панель управления (файл DashboardLayout.vue). Вот шаблон этой страницы:
В этом коде самое важное — тег router-view. Он играет роль контейнера, который содержит все компоненты, соответствующие выводимому маршруту.
Основы работы с компонентами
Компоненты — это базовая составляющая Vue.js-приложений. Они дают нам возможность пользоваться модульным подходом к разработке, что означает разбиение DOM страниц на несколько небольших фрагментов, которые можно многократно использовать на различных страницах.
При проектировании компонентов, для того, чтобы сделать их масштабируемыми и подходящими для повторного использования, нужно учитывать некоторые важные вещи:
- Идентифицируйте отдельный фрагмент функционала, который можно выделить из проекта в виде компонента.
- Не перегружайте компонент возможностями, не соответствующими его основному функционалу.
- Включайте в состав компонента только тот код, который будет использоваться для обеспечения его собственной работы. Например — это код, обеспечивающий работу стандартных для некоего компонента привязок данных, вроде года, пола пользователя, и так далее.
- Не добавляйте в компонент код, обеспечивающий работу с внешними по отношению к компоненту механизмами, например — с некими API.
Здесь, в качестве простого примера, можно рассмотреть навигационную панель — компонент NavBar, содержащий только описания DOM-структур, относящихся к средствам навигации по приложению. Код компонента содержится в файле NavBar.vue:
Вот как этот компонент используется в DashboardLayout.vue:
Взаимодействие компонентов
В любом веб-приложении чрезвычайно важна правильная организация потоков данных. Это позволяет эффективно манипулировать и управлять данными приложений.
При использовании компонентного подхода, при разделении разметки и кода приложения на небольшие части, перед разработчиком встаёт вопрос о том, как передавать и обрабатывать данные, используемые различными компонентами. Ответом на этот вопрос является организация взаимодействия компонентов.
Взаимодействие компонентов в Vue.js-проекте можно организовать с использованием следующих механизмов:
- Свойства (props) используются при передаче данных от родительским компонентам дочерним компонентам.
- Метод $emit () применяется при передаче данных от дочерних компонентов родительским компонентам.
- Глобальная шина событий (EventBus) используется в тех случаях, когда применяются структуры компонентов с глубокой вложенностью, или тогда, когда нужно, в глобальном масштабе приложения, организовать обмен между компонентами по модели «издатель/подписчик».
Для того чтобы разобраться с концепцией взаимодействия компонентов в Vue.js, добавим в проект два компонента:
- Компонент
Add, который будет использоваться для добавления в систему новых задач и для редактирования существующих задач. - Компонент
NoteViewer, предназначенный для вывода сведений об одной задаче.
Вот файл компонента Add (Add.vue):
Add Note
Update Note
Вот файл компонента NoteViewer (NoteViewer.vue):
Created at {{ note.content.createdAt }}
{{ note.content.title }}
{{ note.content.description }}
Теперь, когда компоненты созданы, изучим их разделы .
В объекте props объявлены некоторые объекты с указанием их типов. Это — те объекты, которые мы собираемся передавать компоненту тогда, когда он будет выводиться на некоей странице приложения.
Кроме того, обратите внимание на те участки кода, где используется метод $emit(). С его помощью дочерний компонент генерирует события, посредством которых данные передаются родительскому компоненту.
Поговорим о том, как применять в приложении компоненты Add и NoteViewer. Опишем в файле Home.vue, приведённом ниже, механизмы передачи данных этим компонентам и механизмы прослушивания событий, генерируемых ими:
Теперь, если присмотреться к этому коду, можно заметить, что компонент Add, носящий здесь имя note-editor, применяется дважды. Один раз — для добавления заметки, второй раз — для обновления её содержимого.
Кроме того, мы многократно используем компонент NoteViewer, представленный здесь как note-viewer, выводя с его помощью список заметок, загруженный из базы данных, который мы перебираем с помощью атрибута v-for.
Тут ещё стоит обратить внимание на событие @cancel, используемое в элементе note-editor, которое для операций Add и Update обрабатывается по-разному, даже несмотря на то, что эти операции реализованы на базе одного и того же компонента.
Именно так можно избежать проблемы с масштабированием. Речь идёт о том, что если есть вероятность изменения реализации некоего механизма, то в такой ситуации компонент просто генерирует соответствующее событие.
При работе с компонентами мы пользуемся динамическим внедрением данных. Например — атрибутом :note в note-viewer.
Вот и всё. Теперь наши компоненты могут обмениваться данными.
Использование библиотеки Axios
Axios — это библиотека, основанная на промисах, предназначенная для организации взаимодействия с различными внешними сервисами.
Она обладает множеством возможностей и ориентирована на безопасную работу. Речь идёт о том, что Axios поддерживает защиту от XSRF-атак, перехватчики запросов и ответов, средства преобразования данных запросов и ответов, она поддерживает отмену запросов и многое другое.
Подключим библиотеку Axios к приложению и настроим её, сделав так, чтобы нам не приходилось бы её импортировать при каждом её использовании. Создадим, в папке axios, файл index.js:
import axios from "axios";
const apiHost = process.env.VUE_APP_API_HOST || "/";
let baseURL = "api";
if (apiHost) {
baseURL = `${apiHost}api`;
}
export default axios.create({ baseURL: baseURL });
В файл main.js добавим перехватчик ответов на запросы, предназначенный для взаимодействия с внешним API. Мы будем применять перехватчик для подготовки данных, передаваемых в приложение, и для обработки ошибок.
import HTTP from "./axios";
// Добавить перехватчик ответов
HTTP.interceptors.response.use(
response => {
if (response.data instanceof Blob) {
return response.data;
}
return response.data.data || {};
},
error => {
if (error.response) {
Vue.prototype.$buefy.toast.open({
message: error.response.data.message || "Something went wrong",
type: "is-danger"
});
} else {
Vue.prototype.$buefy.toast.open({
message: "Unable to connect to server",
type: "is-danger"
});
}
return Promise.reject(error);
}
);
Vue.prototype.$http = HTTP;
Теперь добавим в main.js глобальную переменную $http:
import HTTP from "./axios";
Vue.prototype.$http = HTTP;
Мы сможем работать с этой переменной во всём приложении через экземпляр Vue.js.
Теперь мы готовы к выполнению запросов к API, которые могут выглядеть так:
const data = await this.$http.get("notes/getall");
Оптимизация
Представим, что наше приложение доросло до размеров, когда в его состав входят сотни компонентов и представлений.
Это повлияет на время загрузки приложения, так как весь его JavaScript-код будет загружаться в браузер за один заход. Для того чтобы оптимизировать загрузку приложения, нам нужно ответить на несколько вопросов:
- Как сделать так, чтобы компоненты и представления, которые в данный момент не используются, не загружались бы?
- Как уменьшить размер загружаемых материалов?
- Как улучшить время загрузки приложения?
В качестве ответа на эти вопросы можно предложить следующее: сразу загружать базовые структуры приложения, а компоненты и представления загружать тогда, когда они нужны. Сделаем это, воспользовавшись возможностями Webpack и внеся в настройки маршрутизатора следующие изменения:
{
path: "/notes",
name: "Notes",
component: () =>
import(/* webpackChunkName: "home" */ "../views/Home.vue")
}
// Взгляните на /* webpackChunkName: "home" */
Это позволяет создавать для конкретного маршрута отдельные фрагменты с материалами приложения (вида [view].[hash].js), которые загружаются в ленивом режиме при посещении пользователем данного маршрута.
Упаковка проекта в контейнер Docker и развёртывание
Теперь приложение работает так, как нужно, а значит пришло время его контейнеризации. Добавим в проект следующий файл Dockerfile:
# build stage
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
ARG VUE_APP_API_HOST
ENV VUE_APP_API_HOST $VUE_APP_API_HOST
RUN npm run build
# production stage
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
При использовании приложения в продакшне мы размещаем его за мощным HTTP-сервером вроде Nginx. Это позволяет защитить приложение от взломов и от других атак.
Помните о переменной окружения, содержащей сведения о хосте, которую мы объявили, настраивая Axios? Вот она:
const apiHost = process.env.VUE_APP_API_HOST || "/";
Так как это — браузерное приложение, нам нужно установить и передать в приложение эту переменную во время его сборки. Сделать это очень просто, воспользовавшись опцией --build-arg при сборке образа:
sudo docker build --build-arg VUE_APP_API_HOST=://:/ -f Dockerfile -t vue-app-image .
Обратите внимание на то, что вам понадобится заменить , и на значения, имеющие смысл для вашего проекта.
После того, как контейнер приложения будет собран, его можно запустить:
sudo docker run -d -p 8080:80 — name vue-app vue-app-image
Итоги
Мы рассмотрели процесс разработки приложения, основанного на Vue.js, поговорили о некоторых вспомогательных средствах, затронули вопросы оптимизации производительности. Теперь с нашим приложением можно поэкспериментировать в браузере. Вот видео, демонстрирующее работу с ним.
Уважаемые читатели! На что вы посоветовали бы обратить внимание новичкам, стремящимся разрабатывать высокопроизводительные Vue.js-приложения, которые хорошо масштабируются?

