Pull to refresh

Мощный модуль для типизации Vuex

Reading time5 min
Views20K
Мотивом для написания данной статьи послужила другая статья на тему типизации Vue и, соответственно, Vuex. К моему удивлению я не обнаружил там упоминания модуля, который, по моему мнению, является лучшим в своем роде «типизатором» Vuex. Поиск по Хабру, да и вообще по Рунету (на самом деле и в англоязычных источниках не просто сходу найти какие-либо упоминания), увы, не дал никаких результатов. Данная статья не является подробным разбором и многостраничным мануалом по использованию и настройке, но скорее способом поделиться с вами, уважаемые Vue-ниндзя, инструментом, который отлично справляется со своей задачей.

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. Не забудьте поставить звезду этому модулю. :)

Благодарность
В заключение хочу поблагодарить мою любимую жену за терпение, котейку за приятное урчание рядом на столе, соседей за тишину и, конечно же, вас за внимание!
Only registered users can participate in poll. Log in, please.
Используете ли вы Typescript в своих Vue-проектах?
43.97% Да62
22.7% Думаю, что пришло время использовать32
1.42% Использую другие инструменты (Flow)2
31.91% Нет45
141 users voted. 15 users abstained.
Tags:
Hubs:
+18
Comments20

Articles

Change theme settings