[Перевод] Разработка более быстрых приложений на Vue.js

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

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

gcbhg1ndc0-kd3usv2yigyzstjo.jpeg

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

Создание проекта


Прежде чем мы перейдём к разработке — давайте создадим и настроим базовый проект нашего приложения по управлению задачами.

  1. Создадим новый проект, воспользовавшись интерфейсом командной строки Vue.js 3:
    vue create notes-app
  2. Добавим в проект файл 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"
      }
    }
  3. Установим зависимости, описанные в 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 страниц на несколько небольших фрагментов, которые можно многократно использовать на различных страницах.

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

  1. Идентифицируйте отдельный фрагмент функционала, который можно выделить из проекта в виде компонента.
  2. Не перегружайте компонент возможностями, не соответствующими его основному функционалу.
  3. Включайте в состав компонента только тот код, который будет использоваться для обеспечения его собственной работы. Например — это код, обеспечивающий работу стандартных для некоего компонента привязок данных, вроде года, пола пользователя, и так далее.
  4. Не добавляйте в компонент код, обеспечивающий работу с внешними по отношению к компоненту механизмами, например — с некими API.


Здесь, в качестве простого примера, можно рассмотреть навигационную панель — компонент NavBar, содержащий только описания DOM-структур, относящихся к средствам навигации по приложению. Код компонента содержится в файле NavBar.vue:


Вот как этот компонент используется в DashboardLayout.vue:






Взаимодействие компонентов


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

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

Взаимодействие компонентов в Vue.js-проекте можно организовать с использованием следующих механизмов:

  1. Свойства (props) используются при передаче данных от родительским компонентам дочерним компонентам.
  2. Метод $emit () применяется при передаче данных от дочерних компонентов родительским компонентам.
  3. Глобальная шина событий (EventBus) используется в тех случаях, когда применяются структуры компонентов с глубокой вложенностью, или тогда, когда нужно, в глобальном масштабе приложения, организовать обмен между компонентами по модели «издатель/подписчик».


Для того чтобы разобраться с концепцией взаимодействия компонентов в Vue.js, добавим в проект два компонента:

  • Компонент Add, который будет использоваться для добавления в систему новых задач и для редактирования существующих задач.
  • Компонент NoteViewer, предназначенный для вывода сведений об одной задаче.


Вот файл компонента Add (Add.vue):






Вот файл компонента NoteViewer (NoteViewer.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-код будет загружаться в браузер за один заход. Для того чтобы оптимизировать загрузку приложения, нам нужно ответить на несколько вопросов:

  1. Как сделать так, чтобы компоненты и представления, которые в данный момент не используются, не загружались бы?
  2. Как уменьшить размер загружаемых материалов?
  3. Как улучшить время загрузки приложения?


В качестве ответа на эти вопросы можно предложить следующее: сразу загружать базовые структуры приложения, а компоненты и представления загружать тогда, когда они нужны. Сделаем это, воспользовавшись возможностями 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-приложения, которые хорошо масштабируются?

1ba550d25e8846ce8805de564da6aa63.png

© Habrahabr.ru