1.安裝Vuex
npm install vuex --save
2.在src下創(chuàng)建store文件夾,再分別創(chuàng)建index.js actions.js mutations.js文件
- 在
index.js中引入vue,vuex - 再引入
actions.js,mutations.js文件 - 導(dǎo)出默認(rèn)出口
// index.js
import Vue from 'vue'
import Vuex from 'vuex'
import mutations from './mutations'
import actions from './actions'
//使用vuex
Vue.use(Vuex)
//創(chuàng)建vuex實(shí)例
export default new Vuex.Store({
state: {
todos: []
},
actions,
mutations,
})
3.在main.js中引入文件,在vue實(shí)例全局引入store實(shí)例
// main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
store, //把store對象注入所有的子組件
render: h => h(App),
}).$mount('#app')
TodoList實(shí)現(xiàn)
state
Vuex使用單一狀態(tài)樹,每個(gè)應(yīng)用僅包含一個(gè)store實(shí)例。其中state包含全部的應(yīng)用狀態(tài),因此將todos存儲在state中,通過this.$store.state.todos訪問
mutation
要想更改store中狀態(tài)的唯一方法是提交mutation,每個(gè)mutation都有一個(gè)字符串的事件類型 (type) 和 一個(gè)回調(diào)函數(shù) (handler)。這個(gè)回調(diào)函數(shù)就是我們實(shí)際進(jìn)行狀態(tài)更改的地方,并且它會接受state作為第一個(gè)參數(shù),需要對狀態(tài)進(jìn)行改變時(shí)可傳入第二個(gè)參數(shù):
export const mutations = {
addTodo (state, todo) {
state.todos.push(todo);
},
removeTodo (state, todo) {
state.todos.splice(state.todos.indexOf(todo), 1)
},
editTodo (state, { todo, text = todo.text, done = todo.done }) {
todo.text = text
todo.done = done
}
}
通過store.commit觸發(fā),或者使用mapMutations輔助函數(shù)將組件中的methods映射為store.commit調(diào)用(需要在根節(jié)點(diǎn)注入store):
this.$store.commit("addTodo", todo);
this.$store.commit("removeTodo", todo);
this.$store.commit("editTodo", {todo, text:value, done:!todo.done});
action
action類似于mutation,區(qū)別是:
-
mutation是修改state的唯一途徑,action提交mutation,但不能直接修改state -
mutation必須同步執(zhí)行,action可以進(jìn)行異步操作
注冊簡單的action:
export default {
addTodo ({ commit }, text) {
commit('addTodo', {
text,
done: false
})
},
removeTodo ({ commit }, todo) {
commit('removeTodo', todo)
},
changeTodo ({ commit }, todo) {
commit('editTodo', { todo, done: !todo.done })
},
editTodo ({ commit }, { todo, value }) {
commit('editTodo', { todo, text: value })
},
}
action通過store.dispatch觸發(fā),或者使用mapActions輔助函數(shù)將組件的methods映射為store.dispatch調(diào)用(需要先在根節(jié)點(diǎn)注入store):
methods: {
...mapActions([
'editTodo',
'changeTodo',
'removeTodo'
]),
doneEdit (e) {
const value = e.target.value.trim()
const { todo } = this
if (!value) {
this.removeTodo(todo)
} else if (this.editing) {
this.editTodo({
todo,
value
})
this.editing = false
}
},
······
}
Vuex工作流可參考如下圖片:

Vuex官方文檔: Vuex 是什么?
輔助函數(shù)源碼分析
為了避免每次都需要通過this.$store來調(diào)用api,vuex提供了mapState mapMutations mapGetters mapActions createNamespaceHelpers 等api。具體實(shí)現(xiàn)存放在src/helper.js中:
-
一些工具函數(shù)
下面這些函數(shù)都是實(shí)現(xiàn)以上提到的 api 所用到的:
/**
* 統(tǒng)一數(shù)據(jù)格式,將數(shù)組或?qū)ο笳宫F(xiàn)成如下格式
* normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ]
* normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]
* @param {Array|Object} map
* @return {Object}
*/
function normalizeMap (map) {
return Array.isArray(map)
? map.map(key => ({ key, val: key }))
: Object.keys(map).map(key => ({ key, val: map[key] }))
// Object.keys() 方法會返回一個(gè)由一個(gè)給定對象的自身可枚舉屬性組成的數(shù)組
}
/**
* 返回一個(gè)函數(shù),參數(shù)分別為namespace和map,判斷是否存在namespace,統(tǒng)一進(jìn)行namespace處理
* @param {Function} fn
* @return {Function}
*/
function normalizeNamespace (fn) {
return (namespace, map) => {
if (typeof namespace !== 'string') {
map = namespace
namespace = ''
} else if (namespace.charAt(namespace.length - 1) !== '/') {
// charAt() 方法可返回指定位置的字符
namespace += '/'
}
return fn(namespace, map)
}
}
/**
* 通過namespace獲取module
* @param {Object} store
* @param {String} helper
* @param {String} namespace
* @return {Object}
*/
function getModuleByNamespace (store, helper, namespace) {
// _modulesNamespaceMap參考src/store.js
const module = store._modulesNamespaceMap[namespace]
if (process.env.NODE_ENV !== 'production' && !module) {
console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
}
return module
}
-
mapState
為組件創(chuàng)建計(jì)算屬性返回store中的狀態(tài):
/**
* 減少在vue.js中獲取state的代碼
* @param {String} [namespace] - Module's namespace
* @param {Object|Array} states # 對象的項(xiàng)可以是一個(gè)接收state和getter的參數(shù)的函數(shù),你可以在其中對state和getter做一些事情。
* @return {Object}
*/
export const mapState = normalizeNamespace((namespace, states) => {
const res = {}
// 統(tǒng)一數(shù)組格式并遍歷數(shù)組
normalizeMap(states).forEach(({ key, val }) => {
// 返回一個(gè)對象,值都是函數(shù)
res[key] = function mappedState () {
let state = this.$store.state
let getters = this.$store.getters
if (namespace) {
// 獲取module
const module = getModuleByNamespace(this.$store, 'mapState', namespace)
if (!module) {
return
}
// 獲取module的state和getters
state = module.context.state
getters = module.context.getters
}
// Object類型的val是函數(shù),傳遞過去的參數(shù)是state和getters
return typeof val === 'function'
? val.call(this, state, getters)
: state[val]
}
// mark vuex getter for devtools
res[key].vuex = true
})
return res
})
mapState是normalizeNamespace的返回值。首先利用normalizeMap對states進(jìn)行格式的統(tǒng)一,然后遍歷,對參數(shù)的所有state包裹一層函數(shù),返回一個(gè)對象。
-
mapGetters
/**
* 減少獲取getters的代碼
* @param {String} [namespace] - Module's namespace
* @param {Object|Array} getters
* @return {Object}
*/
export const mapGetters = normalizeNamespace((namespace, getters) => {
const res = {}
normalizeMap(getters).forEach(({ key, val }) => {
// The namespace has been mutated by normalizeNamespace
val = namespace + val
res[key] = function mappedGetter () {
if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
return
}
if (process.env.NODE_ENV !== 'production' && !(val in this.$store.getters)) {
console.error(`[vuex] unknown getter: ${val}`)
return
}
return this.$store.getters[val]
}
// mark vuex getter for devtools
res[key].vuex = true
})
return res
})
同樣的處理方式,遍歷getters,只是這里需要加上命名空間,這是因?yàn)樵谧詴r(shí)_wrapGetters中的getters是有加上命名空間的
-
mapMutations
創(chuàng)建組件方法提交mutation
/**
* 減少提交mutation的代碼
* @param {String} [namespace] - Module's namespace
* @param {Object|Array} mutations # 對象的項(xiàng)可以是一個(gè)接受'commit '函數(shù)作為第一個(gè)參數(shù)的函數(shù),它還可以接受另一個(gè)參數(shù)。你可以在這個(gè)函數(shù)中提交變異和做任何其他事情。特別是,您需要從映射函數(shù)傳遞另一個(gè)參數(shù)
* @return {Object}
*/
export const mapMutations = normalizeNamespace((namespace, mutations) => {
const res = {}
normalizeMap(mutations).forEach(({ key, val }) => {
// 返回一個(gè)對象,值是函數(shù)
res[key] = function mappedMutation (...args) {
// Get the commit method from store
let commit = this.$store.commit
if (namespace) {
const module = getModuleByNamespace(this.$store, 'mapMutations', namespace)
if (!module) {
return
}
commit = module.context.commit
}
// 執(zhí)行mutation
return typeof val === 'function'
? val.apply(this, [commit].concat(args))
: commit.apply(this.$store, [val].concat(args))
}
})
return res
})
判斷是否存在namespace后,commit是不一樣的,每個(gè)module都是保存了上下文的,這里如果存在namespace就需要使用那個(gè)另外處理的commit等信息
-
mapActions
/**
* 減少派發(fā)action 的代碼
* @param {String} [namespace] - Module's namespace
* @param {Object|Array} actions # 對象的項(xiàng)可以是一個(gè)接受“派發(fā)”函數(shù)作為第一個(gè)參數(shù)的函數(shù),它還可以接受另一個(gè)參數(shù)??梢栽谶@個(gè)函數(shù)中調(diào)度action并執(zhí)行其他任何操作。
* @return {Object}
*/
export const mapActions = normalizeNamespace((namespace, actions) => {
const res = {}
normalizeMap(actions).forEach(({ key, val }) => {
res[key] = function mappedAction (...args) {
// get dispatch function from store
let dispatch = this.$store.dispatch
if (namespace) {
const module = getModuleByNamespace(this.$store, 'mapActions', namespace)
if (!module) {
return
}
dispatch = module.context.dispatch
}
return typeof val === 'function'
? val.apply(this, [dispatch].concat(args))
: dispatch.apply(this.$store, [val].concat(args))
}
})
return res
})
與mapMutations的處理方式相似
輔助函數(shù)的主要目的是減少代碼量,通過各類api直接在文件中調(diào)用函數(shù)改變狀態(tài),不需要通過this.$store一步步進(jìn)行操作。