Powerful Vuex typing module

    The motive for writing this article was another article on the typing of Vue and, accordingly, Vuex . To my surprise, I did not find mention of a module there, which, in my opinion, is Vuex's best of its kind. The search for Habr, and indeed for the Runet (in fact, and in English sources it is not easy to immediately find any references), alas, did not yield any results. This article is not a detailed analysis and multi-page manual on the use and configuration, but rather a way to share with you, dear Vue-ninja, a tool that does its job perfectly.

    vuex-smart-module


    Who has no time at all: Github .

    The main purpose of the module, as you might have guessed, is the full-format coverage of Vuex storage with types. Both internally and directly in the components themselves. The module is written by the main contributor ( @ktsn ) of the Vuex and vue-class-component libraries .

    Water


    To admit, my path in Typescript began only recently, including and with such things as decorators, so I can not compare this library with other analogs. My attempts to configure and use other tools (for example, vuex-module-decorators ) led me to various problems, which in the end somehow did not allow me to realize what I needed (or I just did not know how to cook them, as they say). I was very lucky with vuex-smart-module - the library appeared at the very moment when I was translating the project (and the repository) into Typescript. Now everything works fine, and the code is pleasing to the eye.

    Examples


    The library, in my opinion, has good documentation that covers all the possible cases that you will encounter (and if not, then in the tests you can find everything else, non-trivial). However, in order to at least somehow dilute the article with code, I will give basic examples of connection and use, a couple of “life” examples, and how it works in conjunction with decorators (there is a nuance there).

    Module creation


    A module is created using classes. This may seem unusually frightening to some, but believe me, you get used to it quickly.

    store / root.ts

    // Импорт базовых классов
    import { Getters, Mutations, Actions, Module } from 'vuex-smart-module'
    // Стейт
    class RootState {
      count = 1
    }
    // Геттеры
    // Необходимо расширить класс типами из RootState
    class RootGetters extends Getters {
      get double() {
        // У инстанса геттера есть свойство `state`
        return this.state.count * 2
      }
      get triple() {
        // Для использования других геттеров есть свойство `getters`
        return this.getters.double + this.state.count
      }
    }
    // Мутации
    // Так же как и геттеры, класс мутаций расширяется типами RootState
    class RootMutations extends Mutations {
      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
    })

    Connection


    /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'
      }
    )
    

    Modules


    Connecting modules is similar to how it happens in regular Vuex. They need to be specified in the modules property of RootStore:

    import FooStore from './modules/foo'
    /* … */
    export default new Module({
      state: RootState,
      getters: RootGetters,
      mutations: RootMutations,
      actions: RootActions,
      modules: {
        FooStore
      }
    })
    

    Use inside a component


    You can use the stor both through the global property this. $ Store, and through mapping, which is very similar to the one in 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)
      }
    })


    Typing


    Example typing commit and 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 {
      setCategories(categories: Category[]) {
        this.state.categories = categories
      }
    }
    class AppActions extends Actions<
      AppState,
      AppGetters,
      AppMutations,
      AppActions
    > {
      async getCategories({params}: {params: IParams}): Promise {
        return categories.get({params}).then(
          ({ data }: { data: Category[] }) => {
            this.commit("setCategories", data)
            return data
          }
        )
      }
    }


    Receptions


    Connect using decorators ( 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)
      }
    }
    

    Using a module inside a module


    /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;
      // Вызывается посли инициализации модуля
      $init(store: Store): void {
        // Создание и сохранение контекста
        this.foo = FooStore.context(store)
      }
      get excited(): string {
        return this.foo.state.value + '!' // -> hello!
      }
    }
    /* … */
    

    Vault reset


    Sometimes it may be necessary to reset the storage to default values, this is done quite simply:

    class FooState {
      /* ... */
    }
    class FooMutations extends Mutations {
      reset () {
          const s = new FooState()
          Object.keys(s).forEach(key => {
            this.state[key] = s[key]
          })
      }
    }

    The final


    I hope you were interested, or at least you found out about this library. Who knows, maybe starting with the next project (or maybe refactoring the current ones just around the corner?) You, like me, will start using vuex-smart-module (or Typescript in general)? Personally, my transition to Typescript was rather painful (for 1.5-2 years, I took on attempts to switch to it 3-4 times at least, but each time I ran into some problems, misunderstanding. I was often haunted by the feeling that development on Typescript takes 2-3 times more time than before, because now you can’t just “quickly sketch it out.” But once, stepping on the “bright side of static typing”, I felt the power of types and how they ultimately allow speed up the development process, which is equally important, debugging the code (perhaps in the same 2- 3 times),

    PS Do not forget to put a star on this module. :)

    Thanks
    In conclusion, I want to thank my beloved wife for her patience, the cat for a pleasant rumbling near the table, the neighbors for silence, and, of course, for your attention!

    Only registered users can participate in the survey. Please come in.

    Do you use Typescript in your Vue projects?

    • 33.6% Yes 32
    • 21% I think it's time to use 20
    • 1% I use other tools (Flow) 1
    • 44.2% No 42

    Also popular now: