-
- Module Introduction
- Why a Different State Management May Be Needed
- Understanding Centralized State
- Using the Centralized State
- Why a Centralized State Alone Wont Fix It
- Understanding Getters
- Using Getters
- Mapping Getters to Properties
- Understanding Mutations
- Using Mutations
- Why Mutations have to run Synchronously
- How Actions improve Mutations
- Using Actions
- Mapping Actions to Methods
- A Summary of Vuex
- Two-Way-Binding v-model and Vuex
- Improving Folder Structures
- Modularizing the State Management
- Using Separate Files
- Using Namespaces to Avoid Naming Problems
- Auto-namespacing with Vuex 21
- Wrap Up Module Resources Useful Links
Module Introduction
Previous state management:
- props
- custom event
- event bus
vue-x:
- better way of state management
- suitable for bigger app
Why a Different State Management May Be Needed
- Traditional way (child A-> parent -> child B): becomes harder with more layers
- Event Bus (child A -> sibling child B): One Bus will quickly get crowded with a lot of different emits
- It is hard to track changes
- suitable for medium sized app
What we need:
- clear separation of concerns
- easy to track changes
Understanding “Centralized State”
Using a central state: we have one file where we store our application state. Any state shared in different components should be stored and fetching in the file.
Using the Centralized State
- create
store
folder insrc
, and createstore.js
install
vuex
npm install –save vuex
Create store instance in
store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
counter: 0
}
});
- register store instance in root vue (
App.vue
)
... // same to sample
import { store } from './store/store';
new Vue({
el: '#app',
store,
render: h => h(App)
})
- use store: store the state in the centralize store, without taking the route over App.vue file, it can communicate with it directly.
e.g.,
increment(){
this.$store.state.counter++;
}
Why a Centralized State Alone Won’t Fix It
Visit it directly is not enough safe ➔ hard to find error
Understanding Getters
Using Getters
export const store = new Vuex.Store({
state: {
counter: 0
},
getters:{
doubleCounter: state => {
return state.counter * 2;
}
}
});
export default{
computed: {
counter(){
return this.$store.getters.doubleCounter;
}
}
}
save time to write duplicate code.
Mapping Getters to Properties
Use mapGetters
to fetch states(getters) to set the properties in vue instance automatically and give access to all needed states ➔ convenient and save time
import { mapGetter } from 'vuex';
export default{
computed: mapGetters([ // it is able to pass an object and map Getters to different names
'doubleCounter',
'stringCounter'
])
}
Merge getters and local properties:
1. merging with ES6’s feature, install babel-preset-stage-2
npm install –save-dev babel-preset-stage-2
- set presets in
.babelrc
{
"presets": [
["es2015", { "modules": false }],
["stage-2"]
]
}
- merge mapGetters and custom local properties:
import { mapGetter } from 'vuex';
export default{
computed: {
...mapGetters([
'doubleCounter',
'stringCounter'
]),
ourOwn(){
...
}
}
}
Understanding Mutations
It is hard to track the changes by accessing the state and manipulating it directly.
Mutation: committed, change state; its usage is similar to getters(like a setter)
If basically we commit such a mutation from one point or component, it will update the state and all components listening to getters will automatically receive the update states.
Using Mutations
Define:
export const store = new Vuex.Store({
state: {
counter: 0
},
getters:{
doubleCounter: state => {
return state.counter * 2;
}
},
mutations:{
increment: state => {
state.counter++;
}
}
});
Use:
methods:{
increment(){
this.$store.commit('increment'); // passing a string
}
}
Merge mutations and custom methods:
import { mapMutation } from 'vuex';
export default{
methods: {
...mapMutations([
'increment',
'decrement'
]),
}
}
change the operation in one central place. more convenient, easy access,
有点像面向对象的get, set, method这样封装数据。
Why Mutations have to run Synchronously
Mutations always have to be synchronously ➔ must not run any asynchronous task in sets up a mutation.
Since state gets change from different sources and you cannot tell order of changes matches to the order of mutations because some mutation might take longer that others.
Mutations must runs changes of state immediately.
How Actions improve Mutations
Action: we dispatch action form the component or by the component where we then we commit the mutation and we only commit the mutation once the asynchronous task is done.
All the changes to our state still happen synchronously but with action we are still able to execute async code for making this change.
(like response to server, long taking async calculation, etc)
Using Actions
Define actions in the store instance:
export const store = new Vuex.Store({
state: {
counter: 0
},
getters:{
doubleCounter: state => {
return state.counter * 2;
}
},
mutations:{
increment: state => {
state.counter++;
}
},
actions: {
increment: context => { // take context as an argument which give access to the commit method
context.commit('increment'); // put out the commit of change
},
// increment: ({commit}) => {
// commit('increment'); // put out the commit of change
},
asyncIncrement: ({commit}) => {
setTimeout(() => {
commit('increment'); // put out the commit of change
}, 1000);
}
}
});
Use in the vue instance:
import { mapActions } from 'vuex';
export default{
methods: {
...mapActions([
'increment',
'asyncIncrement'
]),
}
}
Mapping Actions to Methods
- Call the actions in vue instance’s method
methods:{
...,
increment(){
this.$store.dispatch('increment');
}
}
- Passing a payload with action to mutations
It can pass either a number or string.
It can pass multiple arguments by an object as a payload where all key values are set up as the properties need to use in the mutation.
export const store = new Vuex.Store({
state: {
counter: 0
},
mutations:{
increment: (state, payload) => {
state.counter += payload;
}
},
actions: {
increment: ({commit}, payload) => {
commit('increment', payload);
}
}
});
<template>
<button .. @click="increment(100)">Increment</button>
</template>
A Summary of Vuex
- everything set up in store
- set up initial state in
state
getters
:
- reusable code for accessing
- mapGetters: map getters automatically to access state or property
mutations
: adjust or change state
- take the state as input and optionally to get a payload
- in mutation directly manipulate the state and then overwrite the old state
- can commit mutations directly from components (non-asyn)
- mapMutations
actions
: if run asynchronous task
- set up: first argument is context which is almost the same as the store but does have some minor differences
- need to commit method which is used to commit the mutation
- async code isn’t in mutation: use async code in our actions as long as committing is always done synchronously
- get a payload to pass parameter
- use mapActions to map automatically to access action
no using asynchronous mutations ➔ no need to use action
Two-Way-Binding (v-model) and Vuex
separate
v-model
: bind the getter value to value, and add an input listener to make mutation to the stateSet up
get()
andset()
in computed property
<template>
<input type="text" v-model="value">
<p>{{ value }} </p>
</template>
export default{
computed: {
value: {
get(){
return this.$store.getters.value;
},
set(value){
this.$store.dispatch('update'Value', value);
}
}
}
}
It is rare case, just for v-model
. This solution should be used with caution.
Improving Folder Structures
Use modules: create a modules
folder in store
folder
Modularizing the State Management
Split up over multiple modules which already makes it easier to read
- extra properties (functions) to a new js file in modules
const state = {...};
const getters = {...};
...
- export the properties
export default{
state, // state: state
getters,
...
}
- merge properties in store.js ➔ we only need one centralized store
export const store = new Vuex.Store({
state: ...,
getters: ...,
...,
modules: {
counter, // counter: counter
}
})
Using Separate Files
Sometimes we have some state, actions, getters which don’t really belong into one module.
Separate actions, mutations, getters in different files and then centralize them in the store.js
e.g.,
// actions.js
export const updateValue = ({commit}, payload) => {
commit('updateValue', payload);
}
Import all properties in store.js;
import * as actions from './actions'; // import all data exported in actions.js
import * as mutations from './mutations';
import * as getters from './getters';
export const store = new Vuex.Store({
state: {
value: 0
}
getters,
mutations,
actions,
modules: {
counter
}
})
Now we have a lean store.js. We outsource our centralized tasks and actions, getters, mutations file and we have modules for more specialized things.
Using Namespaces to Avoid Naming Problems
Duplicated keys may happen because properties are defined in different files:
There are two solution to avoid it:
1. create a new file where we can set up some constants which will get unique names which are all stored in this file. // not necessary, it is one of solutions.
But in large app, it might be worth since it really ensure that you are not using same name twice.
2. be careful when assigning names // no examples here
// types.js // it is in store folder
export const DOUBLE_COUNTER = 'counter/DOUBLE_COUNTER' ;
export const CLICK_COUNTER = 'counter/DOUBLE_COUNTER' ;
// counter.js
import * types from '../types';
const getters = {
[types.DOUBLE_COUNTER]: state => {
return state.counter * 2
}
},
...
// use in vue instance
<script>
import {mapGeters} from 'vuex';
import * types from '../types';
export default{
...mapGetters({
doubleCounter: types.DOUBLE_COUNTER,
stringCounter: types.CLICK_COUNTER
})
}
</script>
Auto-namespacing with Vuex 2.1
If you’re using Vuex version 2.1 or higher, you may use its auto-namespacing feature to avoid having to set up all the namespaces manually: https://github.com/vuejs/vuex/releases/tag/v2.1.0
Modules can now be auto-namespaced by using the new namespaced: true
option in store instance.
Wrap Up && Module Resources & Useful Links
Summary:
- communication between different components
- centralized store
- modularing
- separating properties in different files:
- getters
- mutations
- actions
- It’s up to you to decide if it’s worth the extra effort especially when diving into name spaces
- It is worth for
JC project
(very big, almost most of us should be in that team). But not need forGI MWA
(just me develop it)
- It is worth for
Useful links:
- Vuex Github Page: https://github.com/vuejs/vuex
- Vuex Documenation: https://vuex.vuejs.org/en/