Данный пост — это логическое продолжение моего поста/статьи — Как я перестал любить Angular / How I stopped loving Angular.
Рекомендуется к ознакомлению перед прочтением.
Вот уже около года во всех проектах, в которых я участвую, я использую Vue вместо Angular.
В данном посте я поделюсь основными впечатлениями и отличиями после Angular, а также поведаю некоторые вещи из реального опыта использования Vue на боевых проектах.
Вот краткий список основных проблем, беспокоивших меня в Angular на момент написания предыдущей статьи:
Сразу следует оговориться насчет Dependency Injection: после перехода на Vue, все-таки стоит отметить, что в Angular более удобно мокировать внешние зависимости в юнит тестах.
Это полностью зависит от кодовой базы и внутренних best-practice, но нередко во Vue мокировать что-то в тестах получается несколько сложнее, нежели в Angular (пример будет ниже).
Однако я не считаю это серьезным основанием для применения столь чуждого JavaScript'у паттерна на фронт-энде.
И еще одна небольшая оговорка, касательно того, почему был выбран именно Vue, а не React.
Дисклеймер: все пункты ниже ОЧЕНЬ субъективны (поэтому не следует воспринимать их как критику, а лишь как личный взгляд):
сreate-react-app
на мой взгляд, на сегодняшний день, является самым мало-функциональным CLI из всех наиболее популярных фреймворков, уж для React давно стоит создать гораздо более мощную тулзуНемного про основные отличия и впечатления которые я отметил для себя при переходе на Vue.
Первое с чем вы столкнетесь выбрав UI фреймворк, это, конечно же, документация.
Не буду повторно разбирать документацию Angular с ее
Banana in a box
скажу лишь, что во Vue она значительно более простая и доходчивая.
Не буду приводить примеры, т.к. все они могут показаться вам субъективными, тогда как начав читать доки, сразу чувствуется, что они писались для людей.
Также стоит отметить, что доки написаны на 6 языках, хотя хватило бы и английского (считаю, что сейчас любой разработчик должен знать English хотя бы на уровне чтения).
Из моей предыдущей статьи вы могли понять, что раньше я думал что Angular CLI самое лучшее, но, как выяснилось:
@angular
и CLI с разными версиями, что, в свою очередь, приводит к колоссальным проблема при апгрейде версии CLI/angular.С другой стороны Vue CLI 3 — самое крутое CLI на сегодня
Angular использует Zone.js для отслеживания изменений, который monkey-патчит для этого стандартные API, вроде setTimeout
.
Это приводит к определенным проблемам:
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, и появится возможность также отслеживать добавление/удаление пропертей.
В Angular из коробки ядром фреймворка был RxJS, всё — Observable.
Несмотря на мою любовь к Rx, у меня и у многих возникали вопросы, о том, так ли он нужен внутри Angular'а?
Особенно на первых порах очень многие просто превращали Observable в промисы через .toPromise()
.
Да и вообще идея общей шины данных не самая простая для понимания, вдобавок к сложности самого Angular.
В то же время, будучи настолько массивным фреймворком, Angular из коробки не предоставляет имплементации самого популярного на сегодняшний день паттерна работы с данными — State Management.
Существует NgRx, но полноценно юзабельным он стал не так уж давно — как результат, у нас даже есть старый проект с кастомной имплементаций Redux-подобного стора.
А теперь про Vue.
Мало того, что любой фанат RxJS сможет легко подключить его в любой момент, просто добавив новый пакет.
Уже даже есть Vue-Rx, позволяющий использовать RxJS Observabl'ы наравне с data.
Если говорить про State Management, то есть великолепный официальный Vuex.
В свое время с огромным удивлением обнаружил, что помимо него существует также огромное количество альтернатив.
Собственно, тут и проявляется основное достоинство (хотя кому-то может показаться и недостатком) Vue — все можно подключить по необходимости.
Просто добавь:
Чего бы вам недоставало, оно всегда интегрируется просто и быстро.
Причина одной из самых тяжелых психологических травм которую я получил от Angular — роутер.
Несмотря на то, что переписывался он уже трижды, он все еще ужасен (да, я повторяюсь).
Не буду описывать все его проблемы подробно, но так как тема наболевшая, кратко:
Если посмотреть сорцы, можно понять что многие из указанных проблем связаны со сложностью и функциональностью роутера.
То есть, даже не будь в нем странных решений вроде команд, количество фич все равно делает его сложным и тяжелым.
Во 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’ы (об этом ниже).
В 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.
На мой взгляд, именно подобные реальные примеры должны быть описаны в документации.
Вообще, ребята из Vue знают об этой проблеме и решают ее посредством написания Cookbook с рецептами.
Фактически это набор готовых решений на популярные задачи.
Вот например: юнит-тесты,
валидация и работа с HTTP.
Однако, рецепты пока довольно базовые и их сильно не хватает для серьезных задач.
Тесты описаны и для общего понимания этого достаточно, но упомянутое выше мокирование придется искать самому.
На валидации я остановлюсь позже, но вот работа с HTTP точно описана недостаточно глубоко.
Я бы сказал, что Angular приучил меня работать с API бэкенда через сервисы, считаю это хорошим паттерном который сильно облегчает поддержку и переиспользование кода.
Но так как пресловутого DI у нас нет, а самому создавать инстансы сервисов не очень удобно, хотелось бы иметь подобный паттерн в Cookbook.
Данную проблему мы по большей части решили выработкой локальных code conventions и best practices. Ссылки в конце статьи
Я уже множество раз говорил и писал, о том как крут и полезен TypeScript, но факт в том, что его надо уметь готовить.
В Angular все завязано на экспериментальные фичи (декораторы), внутренние классы и сотни (тысячи?) излишних абстракций.
Во Vue возможное использование TypeScript гораздо более логично — оно лишь позволяет расширить возможности разработчика, без необходимости завязываться на те или иные возможности языка.
Однако, на сегодняшний день, использовать 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:
Вот еще некоторые проблемы с TypeScript, разной степени печальности:
this.$store
из компонента возможны фактически с любым payloadНо и Vue.extend(), в свою очередь, имеет некоторые минусы:
// Тип реального значения приходящего в 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, а почти любого 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 нет валидации форм. Хотя, казалось бы, фича весьма востребованная.
Впрочем, это не беда, есть два наиболее популярных решения для этой задачи.
Первое — это весьма схожий механизм с template-driven формами в Angular — vee-validate.
Фактически вы описываете всю логику валидации в HTML.
<input v-validate="'required|email'">
Я бы сказал, что данный подход подойдет только для относительно небольших/несложных форм.
В реальности валидация зачастую бывает весьма навороченной, и описывать все в HTML становится неудобно.
Плюс это не очень красиво работает с Vuex.
Второе решение — 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 — это только библиотека, а не навороченный фреймворк all-in-one.
Из коробки нет:
Да, есть официально поддерживаемые:
Однако, при всем этом, во Vue из коробки есть такая крутая встроенная штуки как Animation ???
В свое время наткнувшись был несколько удивлен богатейшими возможностями по анимации всего что угодно.
Это, пожалуй, гораздо более удобный и мощный тулсет, нежели в том же Angular.
Классный пример из документации:
Небольшая проблема, снова не связанная напрямую с 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 и самим почувствовать разницу.
Чтобы ваш опыт был еще более приятным, вот обещанные выше соглашения по коду от нашей команды, может быть вам пригодятся.
Безусловно они очень субъективны, но иначе быть не может.
Стиль кода и структура приложения
Наш собственный cookbook — сейчас содержит только пример сервиса, но будет расширяться (часть информации на локальных ресурсах).
Это наш Vue seed (TS, кастомные настройки Jest, ESlint и т.д.), в данный момент, до выхода в релиз Vue CLI 3,
он сгенерирован на предыдущей версии и представляет из себя не темплейт а репозиторий.
Готовится версия для разворачивания с помощью Vue CLI 3 через шаблон.
И это все??
Да. Спасибо за внимание.
PS: После прочтения может показаться, что у Vue больше минусов чем плюсов — просто не хочется писать очередную хвалебную статью, ведь их и так полон гугл
PPS: Кстати английский вариант предыдущей статьи был настолько успешен, что у меня даже состоялось прямое общение (видео) с основными виновниками — но работу не предложили =(
К сожалению, не доступен сервер mySQL