Пока Vue 3 официально еще не вышел, а в продакшене в основном 2 версия - я хочу поговорить о типизации и о том, что она все еще не идеальна во Vue. И сегодня мы попробуем создать идеальное приложение с типизацией на typescript сделав упор на code style, пропагандируя vue style guide и прочие обычно не значащие вещи, которые были придуманы умными людьми.
Ремарка
Стоит учитывать, что автор пишет первый свой пост и хотел бы услышать фидбек в комментариях
Почему "идеальное"?
Недавно я осознал всю важность стайл-гайдов, очень уперся в качество кода, настройки автоформатирования (eslint) и многие другие вещи, такие как conventional commits, например. Помимо этого я понял, что мне очень нравится, когда мой код подходит под все международные стандарты и принципы, это ли не признание самого себя как неплохого разработчика (риторический вопрос)? Возможно, вы дошли до этого раньше меня и продвинулись еще дальше и увидите в статье недостатки - добро пожаловать в комментарии с объективной критикой.
Какие проблемы с типизацией у Vue?
Версия 2 принесла и популизировала typescript в компонентах и в хранилище (store), но оставила бреши в связи их между собой, store все еще не подсказывает типизацию в компонентах, и это плохо. Следить в ручную можно за чем угодно, но зачем, если можно это исправить и автоматизировать? Чтобы улучшить типизацию мы будем использовать vue-property-decorator для компонентов и vuex-smart-module для прокидывания типов из стора.
Создание проекта и его настройка
Тут все просто, сначала вызываем vue cli и выбираем галочки.
vue create habratest
Наш выбор - vue2, все features, Vue Router History Mode, Vue Class Components, и другие настройки как на скриншоте ниже. Тесты настраиваем по вкусу, в этой статье их касаться не будем.
Также я рекомендую в графе "Разнести конфиги всего и вся в разные файлы" ставить галочку.
Стили и красота, пока вся тяжесть человеческого прогресса грузится вам в node_modules
В этой статье не будет визуализации и красоты интерфейса, вы можете подключить любую библиотеку внутрь проекта и использовать ее. Мой выбор - vue-bootstrap для легких проектов и quasar для больших. А сегодня красота у нас в коде.
Настройка eslint
Первым делом идем менять настройки. Меняем extends на код ниже - это позволит eslint проверять ваш код более строго - в соответствии с Vue style guide - в соответствии с низшим приоритетом ошибок - recommended.
extends: [
'plugin:vue/recommended',
'@vue/typescript/recommended'
],
Это половина победы, изменение eslint таким образом поможет вам строже соблюдать style guide и в существующем проекте, но будьте осторожны, нередко что-то может пойти не так.
Что в eslint можно отключить
Ничего. Это все поможет вам в написании кода и менять я ничего не рекомендую. Единственное правило, которое можно исключить из проверки - это директива v-html, которая нередко встречается в старой кодовой базе. Но подобное подчеркивание позволит собрать проект и будет лишним напоминанием для вас, так что лучше оставить.
Заодно давайте включим правила для отслеживания одинарных кавычек и удаления лишних точек с запятой - добавим в массив rules в этом же файле следующие строку:
"semi": [2, "never"],
"quotes": [2, "single", { "avoidEscape": true }]
Установка и настройка "правильного" Vuex
npm install vuex-smart-module
Почитать про все библиотеки для типизации Vuex рекомендую тут, возможно другие варианты подойдут вашему проекту больше. Но я выбрал этот модуль, так как он:
От автора vue-property-decorator, который ставится по умолчанию для class-style components;
Максимально близок к обычной версии стора.
Идем src/main.ts и убираем store оттуда. Для типизации нам не нужно определение this.$store
на прототипе, мы будем его импортировать.
Создаем в папке store директорию modules и в ней папку habrModule, в ней файлы: index.ts, actions.ts, getters.ts, mutations.ts, state.ts.
Папка должна выглядеть так, разнесение на модули - важный принцип декомпозиции проекта и поможет вам при увеличении объема кода проекта. (с) Ваш капитан
(!) Также я не использую в корне стора никаких стейтов и геттеров, если это понадобится - попробуйте придумать модуль для этого, например appSettings
Файл src/store/modules/habrModule/state.ts:
export default class HabrState {
value = 'hello';
}
Файл src/store/modules/habrModule/getters.ts:
import { Getters } from 'vuex-smart-module'
import HabrState from './state'
export default class HabrGetters extends Getters<HabrState> {
/**
* Параметризированный greeting, не кэшируется Vuex
* @param name
* @example module.getters.greeting("Habr!")
*/
greeting(name: string): string {
return this.state.value + ', ' + name
}
/**
* Не параметризированный greeting, кэшируется Vuex
* @example module.getters.greetingDefault
*/
get greetingDefault(): string {
return this.getters.greeting('Habr!')
}
}
Файл src/store/modules/habrModule/index.ts:
import { Module } from 'vuex-smart-module'
import getters from './getters'
import state from './state'
const habr = new Module({
state: state,
getters: getters,
})
export default habr
Мы получили маленький, но гордый модуль. Осталось зарегистрировать его в store
Файл src/store/index.ts
import Vue from 'vue'
import Vuex from 'vuex'
import { Module, createStore } from 'vuex-smart-module'
import habr from './modules/habrModule'
Vue.use(Vuex)
const root = new Module({
modules: {
habr,
},
})
const store = createStore(root)
export default store
export const habrModule = habr.context(store)
Другие файлы модуля оставим пустыми, но вы можете их заполнять по необходимости, вся документация есть на гитхаб у модуля.
Чтобы использовать модуль - нужно импортировать его из стора. Далее он будет типизированным.
Пример компонента
<template>
<div class="home">
<img
alt="Vue logo"
src="../assets/logo.png"
>
<HelloComponent msg="Welcome to Your Vue.js + TypeScript App" />
{{ computedTest }}
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import { habrModule } from '@/store'
@Component({
name: 'HomeView',
components: {
HelloComponent: () => import('@/components/HelloComponent.vue'),
},
})
export default class HomeView extends Vue {
get computedTest() {
return habrModule.getters.greetingDefault
}
}
</script>
И подсказочки будут работать
И вроде бы почти закончили! Осталось только...
Рефакторинг
Идем в папке src/views и переименовываем там файлы в HomeView.vue и AboutView.vue, затем в src/components и переименуем там файл в HelloComponent.vue. Также дописываем им
name
в аннотацию компонента.Идем в src/router/index.ts и делаем импорт HomeView.vue динамическим - чтобы было одинаково все и везде, это рекомендация vue style guide.
Поправляем поломавшиеся импорты.
Запускаем автоматический линтер через
npm run lint
.Довольные идем пить кофе и отдыхать.
О чем в статье ни слова
Я постарался добить качество кода до хорошего уровня, следовать большинству заповедей Vue, а также добавил немного от себя, но о некоторых вещах я не рассказал и не использовал:
Component-naming - Vue имеет некоторые правила касающиеся названия компонентов, которые не отслеживаются eslint.
Например компоненты, которые используются лишь раз - следует называть c префиксом The, к примеру
TheNavigationComponent.vue
- так бы я назвал панель навигации, которая лежала бы в корне папки components и больше никуда не импортировалась.Мой принцип названия и разделения между views/components: если компонент импортируется только в роутер - он View, иначе - Component (с опциональным The), суффиксы позволяют выполнять другую рекомендацию - названия компонентов не должны быть из одного слова (Navigation.vue - ALERT!), потому что могут совпадать с названиями html тегов по умолчанию (текущими и будущими).
Названия компонентов в темплейтах:
<MyComponent />
vs<my-component />
Vue разрешает и то и другое, но я рекомендую CamelCase для удобства (например - подсвечивается большинством ide).
Разбиение хранилища на модули - опционально по стайл гайдам, но я приверженец делать его всегда.
Динамические импорты компонентов - это по умолчанию, всегда так делайте (это
Components: () => import(path)
), webpack умный и такие импорты в 90% случаях уменьшат вам время полной загрузки страницы, а в оставшихся 10% - ничего не поменяют, так что оно того стоит. Это просто, одинаково и удобно.Всегда в компонентах прописывайте имя (даже с учетом того, что оно опционально).
Старайтесь названия файлов компонентов согласовывать с названием классов (и именами внутри).
Вызовы к api - ТОЛЬКО из store, об этом вам скажет любой Vue разработчик.
От всего остальное вас должен спасти eslint и голова, но style guide почитайте! :)
Конец
Я надеюсь вам понравилось, качество кода и стайл гайды - это интересно, а также порождает продуктивные дискуссии в командах, обсуждайте такие вещи, это принесет удовлетворение и иногда поднимет самооценку. Но без негатива!
Гитхаб получившегося приложения: github
Также все это получится с небольшими доработками запустить и на Vue 3, у меня получилось, но так как я в нем не до конца еще уверен и разобрался - статья про уходящий Vue 2.
P. S.
Буду рад любому развернутому фидбеку.
Было бы вам интересно почитать нечто подобное для "идеального" тестирования Vue приложений?
UPD
Совет из комментариев, который я забыл включить в статью:
Все действия с api должны проходить через store (через
actions
), при этом сами вызовы (функции) api должны лежать отдельно (моя рекомендация - src/api/index.ts)
av_in
Не будет нормальной типизации с Vuex, и все библиотеки для его типизации костыли. Выкинь его. Как? Берешь что-то вроде { ModuleName: Vue.observable(new MyStorageModuleName()) } и запихиваешь как плагин и/или даже в window.$myStorage.
Выходит действительно хорошо, использую такой подход на продакшене второй год. Vuex собираюсь откапывать для новых проектов только в 5-ой версии, где они обещали все с нуля переписать с нормальной поддержкой ts.
Алсо класс-синтаксис компонентов самому очень нравится, но, наверное, от этого надо уходить, для vue 3 его почти похоронили
karanarqq Автор
Vux покрывает почти все потребности, а «костыли» для типизации — дак сама типизация — костыль)
Нет ничего плохого, чтобы использовать такие библиотеки для типизации. В статье есть ссылка на еще 6 подобных библиотек, и все они лучше, чем навешивать модули на window. У нас в продакшене больше 20 модулей, все на window не навесишь.
А ждать 5 версию vuex — ожидание может затянуться, так что в проде вашу идею я бы не рекомендовал использовать на больших проектах.
Да, класс-синтаксис это большой помощник в типизации компонентов, сам очень доволен.
av_in
Вы меня совсем не поняли.
Ладно, буду писать статью
karanarqq Автор
С удовольствием почитаю)
Про изменения Vue 3 и что там можно отказываться от class components и даже vuex — я еще не совсем в этом уверен и поэтому не спешу с этим.
Glorian
Следует избегать расширения прототипа. Глобальный объект тоже вариант плохой (ну может быть только в целях дебага). При Вашем подходе лучше уж бросить объект через provide/inject
karanarqq Автор
Дойдут руки до переезда на Vue 3 — буду изучать это подробнее, спасибо
andreyiq
Тоже к чему-то подобному пришёл. Во всех новых проектах vuex не использую. По ощущению сам vuex и есть костыль
Almatyn
Касательно класс-синтаксис компонентов — разработчики Vue советуют не использовать, а зачем и что это не совсем понятно. В чем их смысл? Или большой разницы нет, кому как нравится? В своих проектах не использую.
Касательно Vuex
Статья автора меня убедила что Vuex + TypeScrip — остается головной болью. Тут стараешься от всех зависимостей избавиться, а автор советует новую — vuex-smart-module. Я против.
Я свой проект перевел уже давно на Vuejs 3 + TypeScript. От Vuex отказался. Полет нормальный. Vuex и раньше был boilerplate, а с TypeScript вообще ужас.
Работаю сейчас в таком ключе, как в статье за моим авторством — VueJS 3: Глобальное состояние с помощью Composition API. Все стало удобно.