Previous post: Vue & TypeScript First Taste
In my last article, I briefly introduced the use of TypeScript in Vue projects and some of the changes to the way Vue components are written. This article focuses on how to write Vuex in TypeScript more closely to TS object-oriented programming.
options
A quick check shows that there are currently two libraries available for Vue typescript
- vuex-class
- vuex-module-decorators
Vuex-class was last updated 8 months ago and is not updated frequently, so I chose vuex-Module-decorators for learning & validation
Install vuex – module – decorators
Championswimmer. In /vuex-module…
npm install -D vuex-module-decorators
Copy the code
or
yarn install vuex-module-decorators
Copy the code
Babel 6/7
In the project created by @vue/ CLI 3.0, @vue/babel-preset-app already contains the Babel item plug-in.
npm install babel-plugin-transform-decorators --dev
Copy the code
or
yarn install babel-plugin-transform-decorators --dev
Copy the code
TypeScript (3 x) configuration (tsconfig. Json)
In tsconfig.json you need to set:
experimentalDecorators: true,
importHelpers: true
Copy the code
EmitHelpers: true is required in TypeScript 2
configuration
Starting with the [email protected] release, the code is finally released in ES5 format, so the following sections focus on v0.9.2 and earlier
The package eventually generates code in ES2015(ES6) format, so if your Vue project eventually generates ES6, nothing needs to be done. However, if it is ES5(for compatibility with older browsers such as IE9, IE10, IE11), then in the project created via @vue/ CLI, the following configuration needs to be added for conversion:
// in your vue.config.js
module.exports = {
/* ... other settings */
transpileDependencies: ['vuex-module-decorators']}Copy the code
Results show
After using the library, writing Vuex Modules can look like this:
// eg. /app/store/posts.ts
import {VuexModule, Module} from 'vuex-module-decorators'
import {get} from 'axios'
@Module
export default class Posts extends VuexModule {
posts: PostEntity[] = [] // initialise empty for now
get totalComments (): number {
return posts.filter((post) = > {
// Take those posts that have comments
return post.comments && post.comments.length
}).reduce((sum, post) = > {
// Sum all the lengths of comments arrays
return sum + post.comments.length
}, 0)
}
@Mutation
updatePosts(posts: PostEntity[]) {
this.posts = posts
}
@Action({commit: 'updatePosts'})
async function fetchPosts() {
return await get('https://jsonplaceholder.typicode.com/posts')}}Copy the code
It is equivalent to:
// equivalent eg. /app/store/posts.js
module.exports = {
state: {
posts: []},getters: {
totalComments: (state) = > {
return state.posts
.filter((post) = > {
return post.comments && post.comments.length
})
.reduce((sum, post) = > {
return sum + post.comments.length
}, 0)}},mutations: {
updatePosts: (state, posts) = > {
// 'posts' is payload
state.posts = posts
}
},
actions: {
fetchPosts: async (context) => {
// the return of the function is passed as payload
const payload = await get('https://jsonplaceholder.typicode.com/posts')
// the value of 'commit' in decorator is the mutation used
context.commit('updatePosts', payload)
}
}
}
Copy the code
Benefits of type safety
To take advantage of type safety, you cannot dispatch/commit in the usual way… , such as:
MIT (store.com'updatePosts'Await store. Dispatch ('fetchPosts')Copy the code
Writing this way, you don’t take advantage of type safety features, and the IDE doesn’t provide help tips, etc. The best way is to use the getModule accessor to take advantage of the new type safety mechanism:
import { getModule } from 'vuex-module-decorators'Import Posts from '~/store/posts.js' const postsModule = getModule(Posts) // Access the Posts module const Posts = postsModule / / using getters const commentCount = postsModule. TotalComments / / commit mutation postsModule. UpdatePosts (newPostsArray) / / dispatch action await postsModule.fetchPosts()Copy the code
Begin to use
Define a Module
To define a modules, you create a class that extends to VuexModule, and the Module decorates it.
// eg. src/store/MyModule.ts
import { Module, VuexModule } from 'vuex-module-decorators'
@Module
export default class MyModule extends VuexModule {
someField: string = 'somedata'
}
Copy the code
❌import {Module} from ‘vuex’ ✔️ import {Module} from ‘vuex-mole-decorators’
2. Use it in store
In your Store, you can use the class MyModule as a Module
// src/store/index.ts
import Vue from 'vue'
import Vuex from 'vuex'
import MyModule from './MyModule'
Vue.use(Vuex)
export default new Vuex.Store({
state: {},mutations: {},actions: {},modules: {
myMod: MyModule
}
})
Copy the code
Note: The way we use the MyModule class differs from classic object-oriented programming, similar to how Vue-class-Component works. Use the class itself as a module, rather than an object constructed by the class. New MyModule() ❌
3. Access to the state
Direct access through the Store
Import The store
import store from '~/store'
store.state.myMod.someField
Copy the code
Used in componentsthis.$store
access
this.$store.state.myMod.someField
Copy the code
4. Use getModule() to access the Store securely
In addition, for more type-safe access, we can use getModule() to modify the file we wrote earlier:
- src/store/index.ts
- src/store/MyModule.ts
- src/views/useVuex.vue
// src/store/index.ts
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {},mutations: {},actions: {},modules: {}})Copy the code
Note: SRC /store/index.ts does not define any Module
// src/store/MyModule.ts
import { Module, VuexModule } from 'vuex-module-decorators'
import store from './index'
@Module({ dynamic: true, store, name: 'mymod' })
export default class MyModule extends VuexModule {
someField: string = 'somedata'
}
Copy the code
// src/views/useVuex.vue
<template>
<div>
{{hello}}
</div>
</template>
<script lang='ts'>
import { Vue, Component, Emit, Inject, Model, Prop, Provide, Ref, Watch, PropSync } from 'vue-property-decorator'
import { Module, VuexModule, getModule } from 'vuex-module-decorators'
import MyModule from '@/store/MyModule'
const $myMod = getModule(MyModule) // Retrieve a module from the Store
@Component
export default class UseVuex extends Vue {
hello: string = $myMod.someField
}
</script>
Copy the code
The core
1. state
All properties defined in a class are defined as state properties. For example:
import { Module, VuexModule } from 'vuex-module-decorators'
@Module
export default class Vehicle extends VuexModule {
wheels = 2
}
Copy the code
Is equivalent to:
export default {
state: {
wheels: 2}}Copy the code
2. Getters
All get functions are converted to vuex getters, such as:
import { Module, VuexModule } from 'vuex-module-decorators'
@Module
export default class Vehicle extends VuexModule {
wheels = 2
// Get axles will be converted to vuex getters
get axles() {
return wheels / 2}}Copy the code
Is equivalent to:
export default {
state: {
wheels: 2
},
getters: {
axles: (state) = > state.wheels / 2}}Copy the code
3. Mutations
All functions decorated by the @mutation decorator, will be converted to Vuex mutations. Such as:
import { Module, VuexModule, Mutation } from 'vuex-module-decorators'
@Module
export default class Vehicle extends VuexModule {
wheels = 2
@Mutation
puncture(n: number) {
this.wheels = this.wheels - n
}
}
Copy the code
Is equivalent to:
export default {
state: {
wheels: 2
},
mutations: {
puncture: (state, payload) = > {
state.wheels = state.wheels - payload
}
}
}
Copy the code
Pay attention to
Once a function is decorated with @mutation, the this context within the function points to the current state. Therefore, if you want to change the value of some property in state, you can simply write this.item++ instead of the original state.item++
warning
The Muation function cannot be an async function, nor can it be defined using the arrow function, because the context in which the code needs to be rebound during run execution.
4. Actions
All functions decorated by the @Actions decorator are converted to vuex Actions. Such as:
import { Module, VuexModule, Mutation } from 'vuex-module-decorators'
import { get } from 'request'
@Module
export default class Vehicle extends VuexModule {
wheels = 2
@Mutation
addWheel(n: number) {
this.wheels = this.wheels + n
}
@Action
async fetchNewWheels(wheelStore: string) {
const wheels = await get(wheelStore)
this.context.commit('addWheel', wheels)
}
}
Copy the code
Is equivalent to
const request = require('request')
export default {
state: {
wheels: 2
},
mutations: {
addWheel: (state, payload) = > {
state.wheels = state.wheels + payload
}
},
actions: {
fetchNewWheels: async (context, payload) => {
const wheels = await request.get(payload)
context.commit('addWheel', wheels)
}
}
}
Copy the code
Pay attention to
This.context.com MIT (‘mutationName’, mutPayload)
warning
- If you need to run a lengthy task/function in the action function, you are advised to define the task as an asynchronous function. Even if you don’t, the library wraps your function in a Promise and waits for its execution to finish.
- If you insist on using synchronous functions to perform tasks, use them instead
Mutation
- Never use the (=>) arrow function to define the action function, because the this context needs to be dynamically bound at runtime.
Advanced usage
1. Namespaces
Before reading this section, what is the vuex namespace
If you want to use modules as namespaces, add an extra argument to the @Module decorator. For example, the following example code adds a Module with Namespaced for MM
@Module({ namespaced: true.name: 'mm' })
class MyModule extends VuexModule {
wheels = 2
@Mutation
incrWheels(extra: number) {
this.wheels += extra
}
get axles() {
return this.wheels / 2}}const store = new Vuex.Store({
modules: {
mm: MyModule
}
})
Copy the code
Pay attention to
The @Module decorator property field name value must be the same as the new store({modules: {}}) registered Module name.
Keeping these two the same by hand would make coding inelegant, but this is important. We have to convert the: this.store.dispatch (‘action’) call to this.store.dispatch (‘name/action’), and we need to use the correct name in the decorator for it to work
2. Dynamic module
Before reading this section, it is necessary to understand vuex-dynamic-Modulo-registration
Creating a dynamic Module is as simple as passing a few more arguments to the @Module decorator, but only if you first create a Store object and pass it to the Module.
1) create a Store
// @/store/index.ts
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
/* If all modules are created dynamically, this parameter is null */
})
Copy the code
2) Create a dynamic Module
// @/store/modules/MyModule.ts
import store from '@/store' // Notice the store instance
import {Module, VuexModule} from 'vuex-module-decorators'
@Module({dynamic: true, store, name: 'mm'})
export default class MyModule extends VuexModule {
/* Your module definition as usual */
}
Copy the code
Pay attention to
As of now, vuex-module-decorators do not support dynamically nested modules
important
Make sure the store instance is created when you import/required. It must be created before the dynamic Module.
It is important that the Store instance be created in advance. Because the Store instance will be passed as a parameter to the @Module decorator, dynamic Modules will be registered successfully.
A link to the
- github: vue-typescript-skills
- Vue & TypeScript first experience
- vuex-module-decorators
- Vuex-module – Decorators Use case:
- Github.com/Armour/vue-…
- Github.com/xieguangcai…
- Github.com/coding-bloc…
- vuex – modules
- vuex – dynamic-module-registration