Мотивом для написания данной статьи послужила другая статья на тему типизации Vue и, соответственно, Vuex. К моему удивлению я не обнаружил там упоминания модуля, который, по моему мнению, является лучшим в своем роде «типизатором» Vuex. Поиск по Хабру, да и вообще по Рунету (на самом деле и в англоязычных источниках не просто сходу найти какие-либо упоминания), увы, не дал никаких результатов. Данная статья не является подробным разбором и многостраничным мануалом по использованию и настройке, но скорее способом поделиться с вами, уважаемые Vue-ниндзя, инструментом, который отлично справляется со своей задачей.
У кого совсем нет времени: Github.
Главное предназначение модуля, как вы успели догадаться, — это полноформатное покрытие хранилища Vuex типами. Как внутри, так и непосредственно в самих компонентах. Модуль написан основным контрибьютором (@ktsn) библиотек Vuex и vue-class-component.
Признаться, мой путь в Typescript начался еще совсем недавно, в т.ч. и с такими штуками как декораторы, потому не могу сравнить данную библиотеку с другими аналогами. Мои попытки настроить и использовать другие инструменты (например vuex-module-decorators) приводили меня к разным проблемам, которые в итоге так или иначе не позволяли реализовать то, что мне было нужно (либо я просто, как говориться, не умел их готовить). С vuex-smart-module мне очень повезло — библиотека появилась именно в тот момент, когда я переводил проект (и хранилище) на Typescript. Теперь все отлично работает, а код радует глаз.
У библиотеки, на мой взгляд, хорошая документация, которая покрывает все возможные случаи с которым вам придется столкнуться (а если нет — то в тестах можно найти все остальное, нетривиальное). Однако, чтобы хоть как-то разбавить статью кодом, приведу базовые примеры подключения и использования, пару примеров «из жизни», а так же как это работает в связке с декораторами (там есть нюанс).
Создается модуль при помощи классов. Некоторым это может показаться непривычно отпугивающим, но поверьте, привыкаешь быстро.
store/root.ts
/store/index.ts
Подключение модулей аналогично тому, как это происходит в обычном Vuex. Их нужно указать в свойстве modules у RootStore:
Пользоваться стором можно как через глобальное свойство this.$store, так и через мэппинг, который во многом похож на тот, что есть во Vuex:
Пример типизации commit и dispatch:
/store/module/bar.ts
Иногда может потребоваться сбросить хранилище на значения по умолчанию, делается это довольно просто:
Надеюсь, что вам было интересно, ну или, по крайней мере, вы узнали об этой библиотеке. Кто знает, может быть начиная со следующего проекта (а может быть и рефакторинг текущих не за горами?) вы начнете, как и я, использовать vuex-smart-module (или вообще Typescript в целом)? Лично мой переход на Typescript был довольно болезненным (за 1.5-2 года я принимался за попытки перейти на него раза 3-4 минимум, но каждый раз упирался в какие-то проблемы, непонимание. Меня часто преследовало ощущение, что разработка на Typescript занимает в 2-3 раза больше времени, чем раньше, т.к. теперь нельзя просто «по-быстрому набросать». Но однажды, перешагнув на «светлую сторону статической типизации», я ощутил всю мощь типов и то, как они позволяют в конечном итоге ускорить процесс разработки, что не менее важно, отладки кода (пожалуй, в те же самые 2-3 раза), а так же облегчить его дальнейшую поддержку.
P.S. Не забудьте поставить звезду этому модулю. :)
vuex-smart-module
У кого совсем нет времени: Github.
Главное предназначение модуля, как вы успели догадаться, — это полноформатное покрытие хранилища Vuex типами. Как внутри, так и непосредственно в самих компонентах. Модуль написан основным контрибьютором (@ktsn) библиотек Vuex и vue-class-component.
Вода
Признаться, мой путь в Typescript начался еще совсем недавно, в т.ч. и с такими штуками как декораторы, потому не могу сравнить данную библиотеку с другими аналогами. Мои попытки настроить и использовать другие инструменты (например vuex-module-decorators) приводили меня к разным проблемам, которые в итоге так или иначе не позволяли реализовать то, что мне было нужно (либо я просто, как говориться, не умел их готовить). С vuex-smart-module мне очень повезло — библиотека появилась именно в тот момент, когда я переводил проект (и хранилище) на Typescript. Теперь все отлично работает, а код радует глаз.
Примеры
У библиотеки, на мой взгляд, хорошая документация, которая покрывает все возможные случаи с которым вам придется столкнуться (а если нет — то в тестах можно найти все остальное, нетривиальное). Однако, чтобы хоть как-то разбавить статью кодом, приведу базовые примеры подключения и использования, пару примеров «из жизни», а так же как это работает в связке с декораторами (там есть нюанс).
Создание модуля
Создается модуль при помощи классов. Некоторым это может показаться непривычно отпугивающим, но поверьте, привыкаешь быстро.
store/root.ts
// Импорт базовых классов import { Getters, Mutations, Actions, Module } from 'vuex-smart-module' // Стейт class RootState { count = 1 } // Геттеры // Необходимо расширить класс типами из RootState class RootGetters extends Getters<RootState> { get double() { // У инстанса геттера есть свойство `state` return this.state.count * 2 } get triple() { // Для использования других геттеров есть свойство `getters` return this.getters.double + this.state.count } } // Мутации // Так же как и геттеры, класс мутаций расширяется типами RootState class RootMutations extends Mutations<RootState> { increment(payload: number) { // У мутаций так же есть свойство `state` this.state.count += payload } } // Действия // Здесь аналогично расширяется класс // Но есть один нюанс, класс нужно расширить типами этого же класса, явно указав это в параметрах class RootActions extends Actions< RootState, RootGetters, RootMutations, RootActions > { incrementAsync(payload: { amount: number; interval: number }) { // У инстанса действия есть свойства `state`, `getters`, `commit` и `dispatch` return new Promise(resolve => { setTimeout(() => { this.commit('increment', payload.amount) }, payload.interval) }) } } // Экспорт модуля export default new Module({ state: RootState, getters: RootGetters, mutations: RootMutations, actions: RootActions })
Подключение
/store/index.ts
import Vue from 'vue' import * as Vuex from 'vuex' import { createStore } from 'vuex-smart-module' import RootStore from './root' Vue.use(Vuex) export const store = createStore( RootStore, { strict: process.env.NODE_ENV !== 'production' } )
Модули
Подключение модулей аналогично тому, как это происходит в обычном Vuex. Их нужно указать в свойстве modules у RootStore:
import FooStore from './modules/foo' /* … */ export default new Module({ state: RootState, getters: RootGetters, mutations: RootMutations, actions: RootActions, modules: { FooStore } })
Использование внутри компонента
Пользоваться стором можно как через глобальное свойство this.$store, так и через мэппинг, который во многом похож на тот, что есть во Vuex:
import Vue from 'vue' // Импорт корневого стора (тоже самое и с любым другим модулем) // import FooStore from '@/store/modules/foo' import RootStore from '@/store/root' export default Vue.extend({ computed: RootStore.mapGetters(['double']), methods: RootStore.mapActions({ incAsync: 'incrementAsync' }), created() { console.log(this.double) this.incAsync(undefined) } })
Типизация
Пример типизации commit и dispatch:
import { categories } from '@/api' export type Category { attributes: { hasPrice: boolean; icon: string lvl: number name: string slug: string }; id: number } export interface IParams { city_id: number } class AppState { categories: Category[] = [] } /* ... */ class AppMutations extends Mutations<AppState> { setCategories(categories: Category[]) { this.state.categories = categories } } class AppActions extends Actions< AppState, AppGetters, AppMutations, AppActions > { async getCategories({params}: {params: IParams}): Promise<Category[]> { return categories.get({params}).then( ({ data }: { data: Category[] }) => { this.commit("setCategories", data) return data } ) } }
Приемы
Подключение с использованием декораторов (vue-property-decorator)
import { Vue, Component } from "vue-property-decorator" // Импорт корневого стора (тоже самое и с любым другим модулем) // import FooStore from '@/store/modules/foo' import RootStore from "@/store/root" // Обратите внимание, что для того, чтобы все заработало в рамках Typescript, необходимо расширить класс таким образом: const Mappers = Vue.extend({ computed: { ...RootStore.mapGetters(["double"]) }, methods: { ...RootStore.mapActions({ incAsync: 'incrementAsync' }) } }); @Component export default class MyApp extends Mappers { created() { console.log(this.double) this.incAsync(undefined) } }
Использование модуля внутри модуля
/store/module/bar.ts
import { Store } from 'vuex' import { Getters, Actions, Module, Context } from 'vuex-smart-module' // Импорт другого модуля import FooStore from './foo' /* … */ class BarGetters extends Getters { // Объявление контекста foo!: Context<typeof FooStore>; // Вызывается посли инициализации модуля $init(store: Store<any>): void { // Создание и сохранение контекста this.foo = FooStore.context(store) } get excited(): string { return this.foo.state.value + '!' // -> hello! } } /* … */
Сброс хранилища
Иногда может потребоваться сбросить хранилище на значения по умолчанию, делается это довольно просто:
class FooState { /* ... */ } class FooMutations extends Mutations<FooState> { reset () { const s = new FooState() Object.keys(s).forEach(key => { this.state[key] = s[key] }) } }
Финал
Надеюсь, что вам было интересно, ну или, по крайней мере, вы узнали об этой библиотеке. Кто знает, может быть начиная со следующего проекта (а может быть и рефакторинг текущих не за горами?) вы начнете, как и я, использовать vuex-smart-module (или вообще Typescript в целом)? Лично мой переход на Typescript был довольно болезненным (за 1.5-2 года я принимался за попытки перейти на него раза 3-4 минимум, но каждый раз упирался в какие-то проблемы, непонимание. Меня часто преследовало ощущение, что разработка на Typescript занимает в 2-3 раза больше времени, чем раньше, т.к. теперь нельзя просто «по-быстрому набросать». Но однажды, перешагнув на «светлую сторону статической типизации», я ощутил всю мощь типов и то, как они позволяют в конечном итоге ускорить процесс разработки, что не менее важно, отладки кода (пожалуй, в те же самые 2-3 раза), а так же облегчить его дальнейшую поддержку.
P.S. Не забудьте поставить звезду этому модулю. :)
Благодарность
В заключение хочу поблагодарить мою любимую жену за терпение, котейку за приятное урчание рядом на столе, соседей за тишину и, конечно же, вас за внимание!
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Используете ли вы Typescript в своих Vue-проектах?
43.66%Да62
23.24%Думаю, что пришло время использовать33
1.41%Использую другие инструменты (Flow)2
31.69%Нет45
Проголосовали 142 пользователя. Воздержались 15 пользователей.
