Вступление
Данный пост — это логическое продолжение моего поста/статьи — Как я перестал любить Angular / How I stopped loving Angular.
Рекомендуется к ознакомлению перед прочтением.
Вот уже около года во всех проектах, в которых я участвую, я использую Vue вместо Angular.
В данном посте я поделюсь основными впечатлениями и отличиями после Angular, а также поведаю некоторые вещи из реального опыта использования Vue на боевых проектах.
Краткий рекпап предыдущего поста:
Вот краткий список основных проблем, беспокоивших меня в Angular на момент написания предыдущей статьи:
- Ужасный роутер
- Тяжеловесное и практические бесполезное Dependency Injection (см. ниже)
- Крайне спорная система модулей (не используемая ни в одном другом фреймворке)
- Множество лишних и мало полезных абстракций, странный API-design
- Observable как часть фреймворка
...
DI
Сразу следует оговориться насчет Dependency Injection: после перехода на Vue, все-таки стоит отметить, что в Angular более удобно мокировать внешние зависимости в юнит тестах.
Это полностью зависит от кодовой базы и внутренних best-practice, но нередко во Vue мокировать что-то в тестах получается несколько сложнее, нежели в Angular (пример будет ниже).
Однако я не считаю это серьезным основанием для применения столь чуждого JavaScript'у паттерна на фронт-энде.
Почему не про реакт
И еще одна небольшая оговорка, касательно того, почему был выбран именно Vue, а не React.
Дисклеймер: все пункты ниже ОЧЕНЬ субъективны (поэтому не следует воспринимать их как критику, а лишь как личный взгляд):
- Реактивность во Vue просто работает, причем сразу и асинхронно: нет нужды заморачиваться с иммутабельностью и чистыми функциями
(концепция иммутабельности очень крутая, но зачастую крайне многословная — почти всегда это долго и дорого) - JSX плох сразу по нескольким причинам:
- Медленная миграция HTML разметки в код приложения — особенно здесь страдают дизайнеры, которым приходится привлекать разработчика иногда для довольно тривиальных вещей
- Постоянно, даже без особой нужды, приходится дробить все на мелкие компоненты, что отнимает время VS шаблонизация в Angular/Vue – компоненты выносятся по необходимости в любой момент
- В моей памяти еще свежи воспоминания о том, что кондишeны = боль
- Формы адовые – про это позже
сreate-react-app
на мой взгляд, на сегодняшний день, является самым мало-функциональным CLI из всех наиболее популярных фреймворков, уж для React давно стоит создать гораздо более мощную тулзу- У нашей команды был довольно богатый опыт работы с Angular — по ряду схожих факторов войти во Vue им было гораздо проще
Отличия и впечатления после Angular
Немного про основные отличия и впечатления которые я отметил для себя при переходе на Vue.
Документация
Первое с чем вы столкнетесь выбрав UI фреймворк, это, конечно же, документация.
Не буду повторно разбирать документацию Angular с ее
Banana in a box
скажу лишь, что во Vue она значительно более простая и доходчивая.
Не буду приводить примеры, т.к. все они могут показаться вам субъективными, тогда как начав читать доки, сразу чувствуется, что они писались для людей.
Также стоит отметить, что доки написаны на 6 языках, хотя хватило бы и английского (считаю, что сейчас любой разработчик должен знать English хотя бы на уровне чтения).
CLI
Из моей предыдущей статьи вы могли понять, что раньше я думал что Angular CLI самое лучшее, но, как выяснилось:
- Его порой крайне сложно кастомизировать
- Angular и CLI разбит на множество пакетов
@angular
и CLI с разными версиями, что, в свою очередь, приводит к колоссальным проблема при апгрейде версии CLI/angular.
Что и без того делается весьма не просто
С другой стороны Vue CLI 3 — самое крутое CLI на сегодня
- Кастомизация крайне удобна, любой пакет можно добавить в любой момент, благодаря системе плагинов
- Простота и гибкость, благодаря использованию webpack
Zone.js vs Vue Reactivity
Angular использует Zone.js для отслеживания изменений, который monkey-патчит для этого стандартные API, вроде setTimeout
.
Это приводит к определенным проблемам:
- Сложности с нестандартными API, порой довольно сложно разрешимые
- Ужасные стектрейсы
- Вообще говоря ИМХО, это хак, причем далеко не самый элегантный
Vue не нужен Zone.js, отслеживание работатет благодаря превращению всех data/state пропертей в Observer.
В свое время увидев сорцы даже несколько расстроился, что нет никакой магии.
На верхнем уровне все тривиально: Vue проходится по всем пропертям через Object.defineProperty (именно по этой причине не поддерживается IE8 и ниже) и добавляет таким образом геттер и сеттер.
Даже особо нечего добавить...
Однако, у данного подхода есть подводные камни, но они предельно просто описаны и легки для понимания.
Причем понимания не столько Vue, сколько самого JS в его основе.
Во Vue нельзя динамически добавлять новые корневые реактивные свойства в уже существующий экземпляр. Тем не менее, можно добавить реактивное свойство во вложенные объекты, используя метод Vue.set(object, key, value):
var vm = new Vue({
data: {
a: 1
}
})
// теперь vm.a — реактивное поле
vm.b = 2
// vm.b НЕ реактивно
Vue.set(vm.someObject, 'b', 2)
Надо также отметить, что с версии 2.6 Vue не будет иметь и этих проблем, т.к. произойдет переход на Proxy, и появится возможность также отслеживать добавление/удаление пропертей.
Rx и State Management
В Angular из коробки ядром фреймворка был RxJS, всё — Observable.
Несмотря на мою любовь к Rx, у меня и у многих возникали вопросы, о том, так ли он нужен внутри Angular'а?
Особенно на первых парах очень многие просто превращали Observable в промисы через .toPromise()
.
Да и вообще идея общей шины данных не самая простая для понимания, вдобавок к сложности самого Angular.
В то же время, будучи настолько массивным фреймворком, Angular из коробки не предоставляет имплементации самого популярного на сегодняшний день паттерна работы с данными — State Management.
Существует NgRx, но полноценно юзабельным он стал не так уж давно — как результат, у нас даже есть старый проект с кастомной имплементаций Redux-подобного стора.
А теперь про Vue.
Мало того, что любой фанат RxJS сможет легко подключить его в любой момент, просто добавив новый пакет.
Уже даже есть Vue-Rx, позволяющий использовать RxJS Observabl'ы наравне с data.
Если говорить про State Management, то есть великолепный официальный Vuex.
В свое время с огромным удивлением обнаружил, что помимо него существует также огромное количество альтернатив.
Drop In
Собственно, тут и проявляется основное достоинство (хотя кому-то может показаться и недостатком) Vue — все можно подключить по необходимости.
Просто добавь:
Воды- RxJS
- Vuex
- TypeScript
- SCSS
…
Чего бы вам недоставало, оно всегда интегрируется просто и быстро.
Роутер
Причина одной из самых тяжелых психологических травм которую я получил от Angular — роутер.
Несмотря на то, что переписывался он уже трижды, он все еще ужасен (да, я повторяюсь).
Не буду описывать все его проблемы подробно, но так как тема наболевшая, кратко:
- Нет именованных роутов (!) — одно из самых странных решений, которое можно было принять это убрать именованные роуты, тем самым понизив удобство поддержки сложных приложений в разы
- Странная система ивентов, проверять которые нужно по типу,
- Превращение параметров роута в Observable — порой с ними крайне неудобно работать если они вам нужны только 1 раз при старте компонента
- Для навигации были придуманы команды, очень странное и мало полезное решение, более не используемое ни в одном роутере
- Lazy Loading через строковое название модулей, в приложении полностью на TypeScript… без комментариев
… и т.д.
Если посмотреть сорцы, можно понять что многие из указанных проблем связаны со сложностью и функциональностью роутера.
То есть, даже не будь в нем странных решений вроде команд, количество фич все равно делает его сложным и тяжелым.
Во Vue роутер предельно простой и рабочий.
Говоря простой, надо упоминуть, что в свое время я не обнаружил привычного по Angular параметра abstract: true
.
Из коробки нельзя сделать роут без шаблона, но это решается одной строчкой кода — созданием компонента вроде:
// AbstractRoute.vue
<template>
<router-view/>
</template>
Плохо ли то, что подобной функциональности нет из коробки?
И да и нет, ведь с одной стороны важно насколько легко проблема решается, а с другой — сложность и скорость работы самого роутера (в противовес навороченности Angular).
Кстати, вот классная штука:
path: `url/sub-url/:id',
component: MyComponent,
props: true
Теперь у компонента MyComponent
появится props
id
, который будет параметром из URL.
Это элегантное решение, т.к. id
сразу будет реактивным и использовать его в компоненте очень удобно (опять же, просто работает).
Расширение и переиспользование
С одной стороны все компоненты в Angular — TypeScript классы.
Несмотря на это, использовать многие фишки TypeScript зачастую либо неудобно, либо невозможно вовсе.
Это относится в первую очередь к ООП — наследование, абстрактные классы, области видимости.
Основные проблемы возникают с Dependency Injection и AOT компайлером (упаси вас бог столкнуться с ними).
Во Vue же — конфигурация инстанса это объект – с ним просто работать и расширять, рефакторить.
Для переиспользования кода есть мощнейшие штуки — Mixin’ы и Plugin’ы (об этом ниже).
UI компоненты
В Enterprise сегменте компоненты обычно имеют готовый дизайн, мокапы и тд. Чаще всего они пишутся с нуля, сразу будучи заточенными под конкретную задачу/продукт/проект.
Но далеко не всегда у разработчиков есть возможность создавать все с нуля, особенно это касается pet проектов и прототипирования.
Тут на помощь приходят библиотеки готовые UI компонентов, и для Vue их существует уже великое множество: Element, Vuetify, Quasar, Vue-Material, Muse, iView, итд итд.
Особенно я бы отметил Element и Vuetify, впечатления строго положительные: красивые и стабильные компоненты для любых нужд, хорошие доки.
Нам также очень нравится основанный на классном CSS фреймворке Bulma набор компонентов Buefy, особенно удобно его использовать в приложениях на Bulma, куда сторонние компоненты подключаются по необходимости.
В случае Angular — Enterprise-level библиотек компонентов всего пара штук, это в первую очередь Angular Material (Google) и Clarity (VMWare).
К большому сожалению, темпы развития Clarity в последнее время снизились, что еще более расстраивает в плане перспектив Angular в данном вопросе.
Реальный опыт и проблемы
А теперь основные проблемы из реального опыта использования Vue на боевых проектах.
Слишком много свободы
Основной проблемой Vue для серьезных проектов, на сегодняшний день, я бы назвал слишком большую свободу выбора.
С одной стороны, то что одно и то же можно сделать множеством разных способов, это очень здорово.
Но, в реальности, это приводит к тому, что кодовая база становится довольно неконсистентной.
Утрированный пример: кастомную логику на странице можно сделать объявив некий компонент, причем он может быть как локальным
const Component = { created() { // logic ... }}
new Vue({
components: [Component],
так и глобальным (доступным отовсюду).
Vue.component('Global', { created() { // logic ... }})
Можно сделать локальным Mixin который будет реализовывать данную функциональность
const mixin = { created() { // logic ... }}
new Vue({ mixins: [mixin],
причем он (примесь? она?..) опять же может быть глобальным.
Vue.mixin({ created() {// logic ... }})
В конце концов есть плагины, которые делают почти то же самое, что и глобальные миксины.
const MyPlugin = {
install(Vue, options) {
Vue.mixin({ created() { // logic ... }})
}
Безусловно, все эти возможности на самом деле нужны для конкретных задач и очень полезны.
Но какой именно вариант выбрать не всегда очевидно для разработчика, особенно для новичка.
Код ревью необходим
Несмотря на то, что мы используем Vuex, я отметил для себя, что людям порой достаточно тяжело не использовать data() проперти вместо state.
Это вопрос скорее скорости — понятно что добавить что-то в data быстрее, но почти всегда получается так, что потом это необходимо будет выносить в state и тратить на это дополнительное время.
Пожалуй, особенно печальной будет ситуация в проектах, где нет код ревью и есть большое количество junior'ов без большого опыта со Vue.
Могу предположить что работать с таким кодом через несколько месяцев станет совсем неприятно.
Юнит тесты
Также, после Angular, было не очень удобно и очевидно мокировать некоторые вещи в Jest.
Конкретный пример — local storage. Кто-то решил это нагуглив данный issue на гитхабе.
Object.defineProperty(window, 'localStorage', {
value: localStorageMock,
})
Мне решение красивым не показалось, но как потом выяснилось, есть и более элегантное
global.localStorage = localStorageMock;
Да, это не проблема Vue, но проблема экосистемы в сравнении с Angular.
На мой взгляд, именно подобные реальные примеры должны быть описаны в документации.
Cookbook
Вообще, ребята из Vue знают об этой проблеме и решают ее посредством написания Cookbook с рецептами.
Фактически это набор готовых решений на популярные задачи.
Вот например: юнит-тесты,
валидация и работа с HTTP.
Однако, рецепты пока довольно базовые и их сильно не хватает для серьезных задач.
Тесты описаны и для общего понимания этого достаточно, но упомянутое выше мокирование придется искать самому.
На валидации я остановлюсь позже, но вот работа с HTTP точно описана недостаточно глубоко.
Я бы сказал, что Angular приучил меня работать с API бэкенда через сервисы, считаю это хорошим паттерном который сильно облегчает поддержку и переиспользование кода.
Но так как пресловутого DI у нас нет, а самому создавать инстансы сервисов не очень удобно, хотелось бы иметь подобный паттерн в Cookbook.
Данную проблему мы по большей части решили выработкой локальных code conventions и best practices. Ссылки в конце статьи
TypeScript
Я уже множество раз говорил и писал, о том как крут и полезен TypeScript, но факт в том, что его надо уметь готовить.
В Angular все завязано на экспериментальные фичи (декораторы), внутренние классы и сотни (тысячи?) излишних абстракций.
Во Vue возможное использование TypeScript гораздо более логично — оно лишь позволяет расширить возможности разработчика, без необходимости завязываться на те или иные возможности языка.
Проблемы с TypeScript в Vue
Однако, на сегодняшний день, использовать TypeScript со Vue не всегда так уж просто, вот ряд проблем с которым мы столкнулись.
Во-первых они так и не приняли мой Pull Request из-за своих же поломанных тестов =(((
Шутка, конечно же, это только моя личная боль
Два подхода
Основной проблемой TypeScript во Vue я бы назвал то, что есть два разных официальных подхода к его использованию.
Это Vue.extend (тайпинги идущие в комплекте с Vue из коробки и поддерживаемые наравне с основной библиотекой)
import Vue from 'vue'
const Component = Vue.extend({
...
})
и очень схожий с Angular декоратор vue-class-component
import Vue from 'vue'
import Component from 'vue-class-component'
@Component({
template: '<button @click="onClick">Click!</button>'
})
export default class MyComponent extends Vue {
message: string = 'Hello!'
onClick (): void {
window.alert(this.message)
}
}
Лично мне не нравится class-component, и по ряду причин мы используем Vue.extend:
- У class-component есть свои хитрости / недостатки
- Хочется как можно меньше уходить от дефолтных ES6 Vue компонентов
- И использовать код прямо из документации, без необходимости править для TS
- Необходимо, чтобы команда понимала как работает Vue без TypeScript
- Особенно, учитывая что большинство имеет Angular бэкграунд
Другие проблемы
Вот еще некоторые проблемы с TypeScript, разной степени печальности:
- TSlint из коробки не работает с Vue — до сих пор нельзя запустить для .vue файлов. Вообще есть решения через fork-ts-checker, но все они некрасивые
- ESlint для TypeScript еще мягко говоря не супер. И plugin и parser еще в стадии разработки.
Многие core и vue правила ломаются, но основная проблема в том, что возникают крайне непонятные ошибки.
Однако, несмотря на это, мы используем именно его, отключив сломанные правила и подкрутив нужные.
Ссылка на наш конфиг ESlint. - Vuex store не типизирован снаружи, соответственно вызовы
this.$store
из компонента возможны фактически с любым payload
Но и Vue.extend(), в свою очередь, имеет некоторые минусы:
- Нельзя использовать как класс (ваш капитан), соответственно никаких приватных, статических методов и тд.
- Боль c мапперами из Vuex …mapGetters(), …mapState() и тд. При использовании этих мапперов теряется типизация и появляются странные ошибки. Ждем пока зальют решение
- Типизация data() пропертей неудобна, т.к. это функция — каждый раз приходится создавать интерфейс описывающий ее возвращаемое значение
- Честная типизация props вообще практически невозможна, т.к. необходимо объявлять нативный JS тип, а ожидается обычно TypeScript интерфейс, но есть некрасивое решение с кастингом
// Тип реального значения приходящего в myObjectProps
interface MyType = {...}
// Пример типизации data
interface MyComponentData = {
someBooleanProp: boolean;
}
export default Vue.extend({
data(): MyComponentData { // Указание возвращаемого функцией data значения
return {
someBooleanProp: false
};
},
props: {
myObjectProps: Object as MyType // кастинг к TS интерфейсу
},
Формы с Vuex
Вообще формы это проблема не только Vuex, а почти любого State Management паттерна.
Все-таки он предполагает односторонний поток данных, а формы подразумевают двусторонний байдинг.
Vuex предлагает два варианта решения.
Через привязку value
к значению state, а для обновления — событие input с отправкой коммита:
<input :value="message" @input="updateMessage">
// ...
computed: {
...mapState({
message: state => state.obj.message
})
},
methods: {
updateMessage (e) {
this.$store.commit('updateMessage', e.target.value)
}
}
Или все же использовать двусторонний байдинг и v-model
а получение и коммит осуществлять через геттер и сеттер:
<input v-model="message">
// ...
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
Нам второй вариант кажется удобнее и лаконичнее, но все же очень многословным.
Представьте описывать подобным образом форму с 20ю или более полями?
Получается огромное количество кода для такой, казалось бы, примитивной задачи.
Проблема чуть менее актуальна если использовать мапперы, а именно mapGetters()
и mapMutations()
,
но, как я писал выше, они в данный момент с TypeScript плохо работают и пришлось искать другое решение.
Мы написали примитивнейший маппер, который добавляет геттер (геттер из state) и сеттер (коммит мутации):
static mapTwoWay<T>(getter: string, mutation: string) {
return {
get(this: Vue): T {
return this.$store.getters[getter];
},
set(this: Vue, value: T) {
this.$store.commit(mutation, value);
}
};
}
Он позволяет сократить количество кода и описывать поля подобным образом:
stringTags: Util.mapTwoWay<IDatasetExtra[]>(STRING_TAGS, UPDATE_STRING_TAGS)
И, соответственно, использовать v-model
:
v-model="stringTags"
Валидация форм
Что несколько удивило после Angular — из коробки во Vue нет валидации форм. Хотя, казалось бы, фича весьма востребованная.
Впрочем, это не беда, есть два наиболее популярных решения для этой задачи.
vee-validate
Первое — это весьма схожий механизм с template-driven формами в Angular — vee-validate.
Фактически вы описываете всю логику валидации в HTML.
<input v-validate="'required|email'">
Я бы сказал, что данный подход подойдет только для относительно небольших/несложных форм.
В реальности валидация зачастую бывает весьма навороченной, и описывать все в HTML становится неудобно.
Плюс это не очень красиво работает с Vuex.
vuelidate
Второе решение — Vuelidate. Невероятно элегантный способ валидации почти любых компонентов — валидируется сама модель, а не тот или иной input.
Самое забавное, что перед тем как обнаружить этот замечательный пакет мы сами уже было начали писать нечто подобное.
<input v-model="name" @input="$v.name.$touch()">
import { required, email } from 'vuelidate/lib/validators'
export default {
data () {
return {
name: ''
}
},
validations: {
name: {
required,
email
}
}
}
Очень рекомендую при необходимости валидации форм сразу рассматривать Vuelidate — он отлично работает (в том числе с Vuex), легко подключается и кастомизируется.
Основная фишка/проблема Vue
Собственно, в этом и состоит основная проблема (?) Vue — это только библиотека, а не навороченный фреймворк all-in-one.
Из коробки нет:
- HTTP
- Валидации
- i18n
- … и тд?
Да, есть официально поддерживаемые:
- Router
- State
Но, все же это отдельные пакеты, а не ядро.
Однако, при всем этом, во Vue из коробки есть такая крутая встроенная штуки как Animation ???
В свое время наткнувшись был несколько удивлен богатейшими возможностями по анимации всего что угодно.
Это, пожалуй, гораздо более удобный и мощный тулсет, нежели в том же Angular.
Классный пример из документации:
Nightwatch и e2e
Небольшая проблема, снова не связанная напрямую с Vue, но с которой мы столкнулись на реальном проекте.
Из коробки при генерации проекта для e2e тестирования есть выбор между Nightwatch и Cypress.
Несмотря на то, что Cypress лично мне видится как самый классный инструмент для e2e тестирования на сегодня, поддержка браузеров отличных от Chrome до сих пор отсутствует.
Поэтому мы не могли выбрать его для боевого проекта — реальные кастомеры все же используют и другие браузеры.
Однажды наши тесты начали падать на Linux CI по совершенно необъяснимой причине (на Windows все было ок), при этом ошибки не были информативны.
Позже удалось выяснить, что проблема связана с хешами (#) в URL'ах.
А именно такие URL'ы и будут по умолчанию при использовании vue-router
.
К сожалению, это вроде бы проблема селениума или ChromeDriver
, а не Nightwatch (еще один повод смотреть в сторону Cypress и TestCafe), но на текущий момент решением будет "обнулять" url перед открытием хешированных:
.url('data:,')
.url(client.globals.devServerURL + `/#/my-hashed-url`)
Что мы отметили
Скорость разработки
После Angular и огромного количества boilerplate кода, который для него приходилось писать, работа с Vue кажется очень быстрой и простой.
Все решения во фреймворке направлены на минимализм, как с точки зрения функционала, так и с точки зрения Developer Experience. Могу сказать, что наша команда реально стала больше успевать.
Немалую роль здесь сыграло и наличие гигантского комьюнити, которое предлагает множество уже готовых решений для самых разнообразных задач.
Чего нет
Чего действительно пока не хватает Vue — достойной альтернативы React Native.
Ни один из подобных фреймворков для мобильной разработки нельзя с ним сравнить.
Да, для Vue есть NativeScript Vue, но он, на сегодня, значительно менее мощный.
Также есть Weex, но использовать его в реальных проектах я бы пока не стал, так как он еще развивается и не слишком стабилен.
Производительность
Для себя мы также обратили внимание на шикарную скорость работы фрейморка. Особенно это касается первоначального открытия страницы.
Более подробный тест производительности можете посмотреть здесь: http://www.stefankrause.net/js-frameworks-benchmark7/table.html
(помимо "большой тройки" там есть огромное количество других фреймворков и не только).
И напоследок пара ссылок на мнения более авторитетных товарищей: GitLab,
CodeShip, Alibaba, Xiaomi.
Вывод
Из всего описанного выше мы сделали некоторые выводы.
В первую очередь — использование TypeScript с Vue на сегодня имеет немало недостатков, но даже несмотря на них, его использование оправдано.
Мы используем его уже в нескольких проектах, в том числе вот вот выходящих в production.
Более того, четко очертив для себя проблемы и их решения, мы научились работать с TS так же быстро и удобно во Vue, как это делается на JavaScript.
Но все же, хотелось бы, чтобы в более свежих версиях, поддержка TypeScript улучшилась.
Мы выбрали Vue уже около года используем его, за это время мы ни разу не пожалели о своем решении, а количество положительных эмоций от его использования только увеличивается с каждым днем.
Так что, "если вы еще кипятите", очень рекомендую хотя бы попробовать Tide Vue и самим почувствовать разницу.
Наши code conventions
Чтобы ваш опыт был еще более приятным, вот обещанные выше соглашения по коду от нашей команды, может быть вам пригодятся.
Безусловно они очень субъективны, но иначе быть не может.
Стиль кода и структура приложения
Наш собственный cookbook — сейчас содержит только пример сервиса, но будет расширяться (часть информации на локальных ресурсах).
Это наш Vue seed (TS, кастомные настройки Jest, ESlint и т.д.), в данный момент, до выхода в релиз Vue CLI 3,
он сгенерирован на предыдущей версии и представляет из себя не темплейт а репозиторий.
Готовится версия для разворачивания с помощью Vue CLI 3 через шаблон.
И это все??
Да. Спасибо за внимание.
PS: После прочтения может показаться, что у Vue больше минусов чем плюсов — просто не хочется писать очередную хвалебную статью, ведь их и так полон гугл
PPS: Кстати английский вариант предыдущей статьи был настолько успешен, что у меня даже состоялось прямое общение (видео) с основными виновниками — но работу не предложили =(
Автор: MooooM