在開發 複雜vue應用 時,vuex 能夠幫咱們管理 多個組件中的共享狀態。vue
使用 new Vuex.store(options), 能夠構建一個 store 實例。store 是 vuex應用 的核心,經過 store.state 能夠訪問應用中的 共享狀態, 經過 store.getters 能夠訪問 共享狀態派生出的新的狀態,經過 store.commit 方法能夠提交 mutation 以 更改共享狀態,經過 store.dispatch 方法能夠 派發 action 以 異步提交 mutaion。vuex
另外, 若是 應用變得複雜致使store變得比較臃腫 的時候, 咱們能夠將 store 分割成 module, 每一個 module 可擁有本身的 state、getters、mutations、actions 甚至 嵌套子modules。數組
vuex是怎麼安裝的? 安裝過程當中作了哪些工做?緩存
爲何修改state中的數據,會觸發更新?bash
getter的工做原理?app
爲何不建議經過 store.state.xx 的方式直接修改vuex的狀態?異步
命名空間對state、getter、mutation、action的影響ide
嚴格模式是怎麼工做的?函數
state、getter、mutation、action在組件中的使用ui
其餘
使用 vuex 開發 vue應用 的時候,須要先安裝 vuex。
vuex 的安裝過程以下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
複製代碼
vue 在經過 use 安裝 vuex 的時候, 會自動執行 vuex 提供的 install方法。 vuex 提供的 install 方法以下:
// install
function install (_Vue) {
if (Vue && _Vue === Vue) {
{
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
);
}
return
}
Vue = _Vue;
// 執行applyMixin方法
applyMixin(Vue);
}
複製代碼
vuex 提供的 install 方法中,主要過程是執行 applyMixin 方法,具體過程以下:
// applyMixin
function applyMixin (Vue) {
// 獲取當前使用的vue的版本
const version = Number(Vue.version.split('.')[0]);
if (version >= 2) {
// vue2及以上 使用,每個vue實例構建時觸發 beforeCreate 鉤子函數, 執行 vuexInit 方法
Vue.mixin({ beforeCreate: vuexInit });
} else {
// vue1 使用
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
const _init = Vue.prototype._init;
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit;
_init.call(this, options);
};
}
// 每個vue實例構建的時候, 都會執行vuexInit方法
function vuexInit () {
// this -> 當前vue實例
const options = this.$options;
// store injection
if (options.store) {
// 根vue實例 的 $store 屬性
this.$store = typeof options.store === 'function'
? options.store()
: options.store;
} else if (options.parent && options.parent.$store) {
// 組件vue實例, $store屬性, 指向根vue實例的$store屬性
this.$store = options.parent.$store;
}
}
複製代碼
在 applyMixin 方法,全局註冊 beforeCreate 鉤子,即 每個vue實例在構建的時候都會觸發 beforeCreate 鉤子,執行 vuexInit 方法。
執行 vuexInit 方法的時候, 爲 vue實例 建立 $store屬性。不論是 根vue實例 仍是 組件vue實例,$store屬性 都指向經過 new Vuex.store 構建的 store實例,即 全部的 vue實例 的 $store 屬性值都相同。
綜上, vuex 安裝的時候就作了 一件事:給 vue全局註冊 beforeCreate。當建立 vue實例 的時候,觸發 beforeCreate,給 vue實例 添加 $store 屬性,指向經過 new Vuex.store 構建的 store實例。
在使用 vuex 開發 vue應用 的時候, 咱們會定義 state, 而後在 組件 中經過 計算屬性(computed) 使用定義好的 state。 以下:
var option = {
state: {
money: 10000
},
mutations: {
setMoney(state, money) {
state.money = money
}
}
}
var bill = {
data() {
return {...}
},
computed: {
finalMoney() {
// money的單位爲 釐
return parseInt(this.$store.state.money / 1000)
}
},
methods: {
setDisount() {
this.$store.commit('setMoney', this.money)
}
}
...
}
複製代碼
在上面的 示例 中,帳單組件 在計算 費用 的時候,使用了 vuex狀態 - money。 當經過 組件方法 setMoney 修改 vuex狀態 - money, 帳單組件以前計算的費用 也會 更新。
那麼問題來了, vuex的響應式更新是怎麼觸發的?
其實,vuex的響應式原理 是基於 vue的響應式原理 實現的。
在 構建vue實例 的時候,若是 配置項 中包含 數據屬性data, 會 深度遍歷data中的每個屬性,爲 每個屬性 創建一個 deps列表,並經過 defineProperty 方法爲 每個屬性 添加 getter、setter。若是 template模板、計算屬性、監聽屬性 使用了 data屬性, 會 觸發data屬性的getter,對應的 watcher 會添加到 data屬性的deps列表 中。 當 data屬性 發生變化時, 觸發setter,通知deps列表中的watcher更新 ,而後 從新渲染界面、返回新的計算屬性的值、觸發監聽callback。
vuex響應式更新, 是在使用 new Vuex.store(options) 構建 store實例 的時候 實現 的,經歷的 主要流程 以下:
創建 根module 以及 子modules。
在構建 store實例 的時候,首先會根據傳入的 配置項 options(state、getters、mutation等) 生成一個 root module對象。若是 options 中包含 modules以及嵌套modules, 那麼會遍歷 modules 及嵌套 modules, 創建對應的 module 對象。
每個 module 對象 都包含各自的 state、getters、actons、mutations 以及 嵌套子modules。
module 對象 會經過 children屬性(array), 收集 關聯的 子module 對象。
將 嵌套子modules 中的 state 收集到 根module 的 state 中。
// options
var option = {
state: {
name: ''
},
modules: {
trade: {
state: {
money: 1000
},
modules: {
loan: {
state: {
discount: 0.8
},
modules: {
confirm: {
state: {
num: 2
}
}
}
},
product: {
state: {
discount: 0.9
}
}
}
}
}
}
// 收集 嵌套子module state 之後的 根module state
state: {
name: '',
trade: {
loan: {
discount: 0.8,
confirm: {
num: 2
}
},
product: {
discount: 0.9
}
}
}
複製代碼
使用 new Vue 爲 store實例 構建一個 vue實例 - _vm。
構建 vue實例 的時候, 根module 的 state屬性 會做爲 data屬性 使用。
store._vm = new Vue({
data: {
$$state: state // state 爲 收集子module state 之後的 根module state
}
});
複製代碼
vue實例 構建完成之後, $$state 中 每個屬性 都會變成一個 響應式屬性,擁有 getter、setter, 維護一個 deps列表。
在 vue組件 中,咱們能夠經過 this.$store.state 的方式訪問 _vm實例 中的 $$state屬性,原理以下:
class Store {
...
// 訪問 store實例的 state屬性,實質爲訪問 store._vm._data.$$state 屬性
get state () {
return this._vm._data.$$state
}
}
複製代碼
若是 template模板、computed屬性、watcher屬性 中經過 this.$store.state 的方式使用了 某一個vuex狀態, 會 觸發 vuex狀態 在 _vm.$$data 中的 同名屬性 的 getter, 對應的 watcher 會添加到 同名屬性 的 deps列表 中。 當 修改vuex狀態 時, 觸發vuex狀態在 _vm.$$data 中的 同名屬性 的 setter,通知 deps列表 中的 watcher更新,而後 從新渲染界面、返回新的計算屬性的值、觸發監聽callback。
getter,能夠認爲是 store 的 計算屬性。就像 vue的計算屬性 同樣, getter的返回值會被緩存,只有 依賴的state狀態 發生變化, 纔會 從新計算。
getter 是基於 vue的計算屬性(computed) 實現的。
vue 的 計算屬性 的 實現原理 以下:
構建 vue實例 的時候,須要一個 options配置項,包含 data、props、computed、methods 等屬性。 若是 options 中有 computed屬性,須要 遍歷computed中的屬性,爲 每個屬性 創建一個 watcher。此外,根據 每個computed屬性, 經過 defineProperty 的方式爲 vue實例 創建一個 同名屬性,設置 getter。 當經過 vue實例 訪問 計算屬性 的時候,觸發getter,執行計算屬性對應的方法。
每個 計算屬性,都對應一個 watcher。 watcher 有一個標誌屬性: dirty。 當 dirty 屬性的值爲 true 時,獲取 計算屬性 的值, 須要 執行計算屬性對應的方法並緩存返回的結果; 當 dirty 的值爲 false 時,返回 緩存的值。
watcher 在 初次構建 的時候,dirty 值爲默認爲 true。
執行計算屬性對應的方法 之後, dirty 的值會置爲 false。
第一次 使用 計算屬性 的時候, 因爲 watcher.dirty 的值爲 true,須要 使用計算屬性對應的方法。執行方法的時候, 會讀取 響應式屬性(data、props) 的值, 觸發 響應式屬性 的 getter方法。 計算屬性 的 watcher 會被添加到 響應式屬性 的 deps列表 中。 此外, watcher.dirty 的值會置爲 false。
若是依賴的響應式屬性發生變化,觸發 setter方法, 通知 deps列表 中的 watcher 更新。 此時 watcher.dirty 的值會置爲 true。 下一次 使用 計算屬性 的時候, 執行計算屬性對應的方法從新計算。
若是依賴的響應式屬性沒有變化, watcher.dirty 的值一直爲 false。下一次 使用 計算屬性 的時候,直接返回 緩存的上一次計算結果。
vuex 的 getter, 也是在使用 new Vuex.store(options) 構建 store實例 的時候 實現 的,經歷的主要過程以下:
創建 根module及 子modules。
將 子modules 中的 state 收集到 根module 的 state 中。
將 根module 及 子modules 中的 getters 收集到 store實例 的 _wrappedGetters 對象中。
以 store實例 的 _wrappedGetters 對象做爲 computed配置項,根module 的 state對象 做爲 data配置 項構建 vue實例 - _vm。
store._vm = new Vue({
data: {
$$state: state // state 爲 收集子module state 之後的 根module state
},
computed: _wrappedGetters
});
複製代碼
給 store實例 添加 getters 屬性。 遍歷 _wrappedGetters 中屬性,經過 defineProperty 的方式,爲 store.getters 添加屬性,並設置 getter方法,使咱們能夠經過 store.getters.xx 的方式訪問 store._vm 的 同名計算屬性。
當咱們在 組件 中經過 this.$store.getters.xx 的方式訪問 vuex 定義的 getter 時, 等價於訪問 this.$store._vm 中的 同名計算屬性。
第一次 訪問 vuex 的 getter 時,同名計算屬性 對應的 watcher.dirty 的值爲 true,須要執行計算屬性對應的方法。 執行的時候, 會 讀取依賴的state的值, 觸發state的getter方法,而後讀取 this.$store._vm._data.$$data 中 同名響應式屬性 的值,觸發響應式屬性的getter方法。此時,同名計算屬性 的 watcher 會被添加到 響應式屬性的deps列表 中。同名計算屬性 對應的方法執行完畢之後,結果會 緩存, watcher.dirty 的值置爲 false。
若是 vuex 的 getter 依賴的 state 發生變化,this.$store._vm._data.$$data 中對應的 同名響應式屬性 的 setter 會被觸發,而後 通知deps列表中的watcher更新。vuex 的 getter 的 同名計算屬性 的 watcher.dirty 的值置爲 true。 下一次訪問 vuex 的 getter 時,根據 依賴的state,返回 新的值。
若是 vuex 的 getter 依賴的 state 一直沒有發生變化,對應的 同名計算屬性 的 watcher.dirty 的值一直爲 false。 下一次訪問 vuex 的 getter 時,返回 同名計算屬性緩存的結果。
使用 new Vuex.store 構建 store實例 的時候, 若是 strict配置項 的值爲 true, 則啓用 嚴格模式。
啓用 嚴格模式 之後, 若是 state變動不是由 mutation 引發 的,則會拋出 異常。
直接修改state 和 經過mutation修改state, 都是 修改state,爲何 直接修改state就會拋出異常 呢?
在 vuex 中, 有一段源碼涉及到 嚴格模式,以下:
function enableStrictMode (store) {
store._vm.$watch(function () { return this._data.$$state }, () => {
{
assert(store._committing, `do not mutate vuex store state outside mutation handlers.`);
}
}, { deep: true, sync: true });
}
複製代碼
構建 store實例 的時候, 咱們會爲 store實例 構建一個 vue實例:_vm。 若是是 嚴格模式,執行 enableStrictMode 方法。
在 enableStrictMode 方法中, _vm 會監聽 $$state。 若是 $$state 發生變化,則執行 callback。
不論是 直接修改state 仍是 經過mutation修改state,最後都會致使 $$state 發生變化,而後 觸發callback。 不一樣的是,使用 mutation 時,store._committing 值爲 true,不會拋出異常,而 直接修改 時,store._committing 值爲 false, 會拋出異常。
默認狀況下, 不啓用嚴格模式。 即若是構建 store實例* 的時候,未設置strict配置項,不啓用嚴格模式。
注意: 生產環境下, 不要啓用嚴格模式。
vuex 建議咱們經過 提交mutation 的方式 修改state, 緣由以下:
嚴格模式下(strict: ture), 若是經過 store.state.xx = xxx 的方式 修改state, 會 拋出異常;
啓用dev-tools(devtools: true) 後, 若是經過 store.state.xx = xxx 的方式 修改state, state的變化 沒法被 dev-tools 追蹤。
開啓 dev-tools 之後, 當咱們經過 提交mutation 的方式 修改state 時, state的變化 能夠被 dev-tools 追蹤到。 在 dev-tools 的 vuex列表 中,咱們能夠清晰明瞭的看到每次 commit 操做。
構建 store實例 的時候, dev-tools 插件會經過 store.subscribe 方法 訂閱 store 的 mutation,即 註冊一個 callback。 當執行 store.commit(type, payload) 時, 先觸發 type 對應的 mutations, 而後再觸發 callback, 通知 dev-tools 追蹤 mutation。 觸發 callback時,傳入 mutation 和 通過 mutation 後的狀態做爲參數。
默認狀況 下,模塊內部的 action、mutation 和 getter 是 註冊在全局命名空間 的,這樣使得 多個模塊 可以對 同一 mutation 或 action 做出響應。
若是 但願你的模塊具備更高的 封裝度 和 複用性,你能夠經過添加 namespaced: true 的方式使其成爲帶 命名空間 的模塊。當 模塊 被 註冊 後,它的全部 getter、action 及 mutation 都會 自動根據模塊註冊的路徑調整命名。
命名空間 對模塊 getter、action、mutation 的影響, 咱們經過一個 示例 來講明:
var option = {
// root module
state: { name: 'root' },
getters: { getName: state => state.name },
mutations: { setName: (state, name) => { state.name = name }},
actions: { setName: (context, name) => { context.commit('setName', name) }},
modules: {
// module A
moduleA: {
namespaced: true,
state: { name: 'moduleA' },
getters: { getName: state => state.name },
mutations: { setName: (state, name) => { state.name = name }},
actions: { setName: (context, name) => { context.commit('setName', name) }}
modules: {
// module C
moduleC: {
state: { name: 'moduleC' },
getters: { getName: state => state.name },
mutations: { setName: (state, name) => { state.name = name }},
actions: { setName: (context, name) => { context.commit('setName', name) }}
},
// module D
moduleD: {
namespaced: true,
state: { name: 'moduleD' },
getters: { getName: state => state.name },
mutations: { setName: (state, name) => { state.name = name }},
actions: { setName: (context, name) => { context.commit('setName', name) }}
}
}
},
// module B
moduleB: {
state: { name: 'moduleB' },
getters: { getName: state => state.name },
mutations: { setName: (state, name) => { state.name = name }},
actions: { setName: (context, name) => { context.commit('setName', name) }}
}
}
}
複製代碼
在上面的示例中,store 被切割成 root module、 moduleA、 moduleB、 moduleC、 moduleD。
root module,無論 namespced 的值爲 true 或者 false, 它的 命名空間 都是 空, 即爲 全局命名空間。
moduleA,namespaced 的值爲 true, 它的 命名空間爲 'moduleA'。
moduleB,namespaced 的值 不是true, 它會 繼承 父module 即 root module 的 命名空間,即它的 命名空間 爲 全局命名空間。
moduleC,namespaced 的值 不是true, 它會 繼承 父module 即 moduleA 的 命名空間,即它的 命名空間 爲 'moduleA'。
moduleD,namespaced 的值爲 true, 它的 命名空間爲 'moduleA/moduleD'。
命名空間 對 getters、 mutations、actions 的使用會有 影響:
getter
在構建 store實例 的時候,會將各個 module 的 getters 收集到 store._wrappedGetters 中。
store._wrappedGetters 是一個 對象, 屬性名 爲:命名空間/getter名, 對應的 屬性值是一個 函數 - 通過包裝之後的getter。
因爲 _wrappedGetters 是一個 對象, 若是 存在屬性名相同的getter,會拋出警告。
在上面的示例中, _wrappedGetters 中, 收集到的 getter 以下:
_wrappedGetters = {
'getName': function() {...}, // 根 module 中的 getter
'moduleA/getName': function() {...}, // moduleA 的 getter
'moduleA/moduleD/getName': function() {...} // moduleD 的 getter
}
複製代碼
對於 moduleB, 命名空間爲 全局命名空間,則對應的屬性名爲 getName, 此時 _wrappedGetters 中已經存在 同名屬性, 沒法添加且拋出警告。
moduleC 的狀況也同樣,命名空間爲 moduleA, 對應的屬性爲 moduleA/getName,_wrappedGetters 中已經存在 同名屬性, 沒法添加且拋出警告。
在 組件 中,咱們能夠經過 store.getters.xx 訪問 getter,xx 會對應 _wrappedGetters 中的 屬性名, 以下:
this.$store.getters.getName // 訪問 根module 的 getName
this.$store.getters['moduleA/getName'] // 訪問 moduleA 的 getName
this.$store.getters['moduleA/moduleD/getName'] // 訪問 moduleD 的 getName
複製代碼
moduleB、moduleC 的 getName 沒有收集到 _wrappedGetters 中,在 組件 中 沒法訪問。
mutation
在構建 store實例 的時候,會將各個 module 的 mutations 收集到 store._mutations 中。
store._mutations 是一個 對象, 屬性名 爲:命名空間/mutation名, 對應的 屬性值是 數組,數組元素 爲 通過包裝的mutation。
在上面的示例中, _mutations 中, 收集到的 mutations 以下:
_mutations: {
// fn_root 對應 根module 的 setName, fnB 對應 moduleB 的 setName
'setName': [fn_root, fnB],
// fnA 對應 moduleA 的 setName, fnC 對應 moduleC 的 setName
'moduleA/setName': [fnA, fnC],
// fnD 對應 moduleD 的 setName
'moduleA/moduleD/setName': [fnD]
}
複製代碼
moduleB 的 命名空間 爲 全局命名空間,對應的屬性名爲 setName, 在 _mutations 已存在, 將 mutation 添加到 setName 對應的 數組中。
moduleC 的 命名空間 爲 moduleA,對應的屬性名爲 moduleA/setName, 在 _mutations 已存在, 將 mutation 添加到 moduleA/setName 對應的 數組中。
在 組件 中,咱們能夠經過 store.commit(type, payload) 的方式 提交mutation,type 會對應 _mutations 中的 屬性名,以下:
// 觸發 根module、moduleB 的 setName
this.$store.commit('setName', 'xxx')
// 觸發 moduleA、 moduleC 的 setName
this.$store.commit('moduleA/setName', 'xxx')
// 觸發 moduleD 的 setName
this.$store.commit('moduleA/moduleD/setName', xxx)
複製代碼
在上面的示例中, root module 和 moduleB 的 命名空間 相同,提交 'setName' 時,root module 和 moduleB 中的 mutation - setName 都會觸發; moduleA 和 moduleC 的 命名空間 相同, 提交'moduleA/setName' 時, moduleA 和 moduleC 中的 mutation - setName 都會觸發。
actions
在構建 store實例 的時候,會將各個 module 的 action 收集到 store._actions 中。
store._actions 是一個 對象, 屬性名 爲:命名空間/action名, 對應的 屬性值是 數組,數組元素 爲 通過包裝的action。
在上面的示例中, _actions 中, 收集到的 actions 以下:
_actions: {
// fn_root 對應 根module 的 setName, fnB 對應 moduleB 的 setName
'setName': [fn_root, fnB],
// fnA 對應 moduleA 的 setName, fnC 對應 moduleC 的 setName
'moduleA/setName': [fnA, fnC],
// fnD 對應 moduleD 的 setName
'moduleA/moduleD/setName': [fnD]
}
複製代碼
moduleB 的 命名空間 爲 全局命名空間,對應的屬性名爲 setName, 在 _actions 已存在, 將 action 添加到 setName 對應的 數組中。
moduleC 的 命名空間 爲 moduleA,對應的屬性名爲 moduleA/setName, 在 _actions 已存在, 將 action 添加到 moduleA/setName 對應的 數組中。
在 組件 中,咱們能夠經過 store.dispatch(type, payload) 的方式 派發 action,type 會對應 _actions 中的 屬性名,以下:
// 觸發 根module、moduleB 的 setName
this.$store.dispatch('setName', 'xxx')
// 觸發 moduleA、 moduleC 的 setName
this.$store.dispatch('moduleA/setName', 'xxx')
// 觸發 moduleD 的 setName
this.$store.dispatch('moduleA/moduleD/setName', xxx)
複製代碼
在上面的示例中, root module 和 moduleB 的 命名空間 相同,派發 'setName' 時,root module 和 moduleB 中的 action - setName 都會觸發; moduleA 和 moduleC 的 命名空間 相同, 派發 'moduleA/setName' 時, moduleA 和 moduleC 中的 action - setName 都會觸發。
一般,咱們會在 vue組件 中經過 計算屬性 來訪問所需的 狀態 - state。
具體的方式,咱們經過一個 示例 來講明。
var options = {
state: {
name: 'zhangsan'
},
modules: {
trade: {
namespaced: true,
state: {
type: 'waiting'
},
modules: {
product: {
namespaced: true,
state: {
id: 1
}
}
}
},
loan: {
namespaced: true,
state: {
money: 100000
}
}
}
}
複製代碼
直接訪問
咱們能夠經過 vm.$store.state.xx 的方式直接訪問所需的 狀態 - state。
var bill = {
data() {
return {...}
},
computed: {
name() {
return this.$store.state.name
},
tradeType() {
return this.$store.state.trade.type
},
productId() {
return this.$store.state.trade.product.id
},
loanMoney() {
return parseInt(this.$store.state.loan.money / 1000)
}
}
}
複製代碼
經過 mapState 輔助函數
咱們可使用 輔助函數 - mapState 幫助咱們生成 計算屬性。
var mapState = Vuex.mapState
var bill = {
data() {
return {...}
},
computed: {
...mapState({ // 全局命名空間
name: 'name',
tradeType: state => state.trade.type // 全局 state
}),
...mpaState('trade/product', { // trade/product 命名空間
productId: 'id'
}),
...mapState('loan', { // loan 命名空間
loanMoney: state => parseInt(state.money / 1000) // 局部 state
})
}
}
複製代碼
使用 createNamespacedHelpers 輔助函數
咱們可使用 輔助函數 - createNamespacedHelpers、mapState 幫助咱們生成 計算屬性。
createNamespacedHelpers 能夠幫助咱們生成 基於命名空間 的 輔助函數。
// 基於 全局 命名空間 的 mapState
var mapState = Vuex.mapState
// 基於 trade 命名空間 的 mapState
var tradeMapState = Vuex.createNamespacedHelpers('trade').mapState
// 基於 trade/product 命名空間 的 mapState
var productMapState = Vuex.createNamespacedHelpers('trade/product').mapState
// 基於 loan 命名空間的 mapState
var loanMapState = Vuex.createNamespacedHelpers('loan').mapState
var bill = {
data() {
return {...}
},
computed: {
...mapState(['name']), // 計算屬性名爲 name, 返回值爲 this.$store.state.name
...tradeMapState({
tradeType: 'type' // 計算屬性名爲 tradeType, 返回值爲 this.$store.state.trade.type
}),
...productMapState({
productId: 'id'
}),
...mapState({
loanMoney: state => parseInt(state.money / 1000)
})
}
}
複製代碼
vuex 的 getter 的使用,和 state 相似,咱們一樣以一個示例來講明。
var options = {
state: {
name: 'zhangsan'
},
getters: {
getName(state) {
return state.name
}
},
modules: {
trade: {
namespaced: true,
state: {
type: 'waiting'
},
getters: {
getTradeType(state) {
return state.type
}
},
modules: {
product: {
namespaced: true,
state: {
id: 1
},
getters: {
getProductId(state) {
return state.id
}
}
}
}
},
loan: {
namespaced: true,
state: {
money: 100000
},
getters: {
getLoanMoney(state) {
return state.money
}
}
},
confirm: {
state: {
num: 100
},
getters: {
getConfirmNum(state) {
return state.num
}
}
}
}
}
複製代碼
直接使用
咱們能夠經過 vm.$store.getters.xx 的方式直接訪問所需的 getter。
var mapGetters = Vuex.mapGetters
var bill = {
data() {
return {...}
},
computed: {
getName() {
return this.$store.getters.getName
},
getTradeType() {
return this.$store.getters['trade/getTradeType']
},
getLoanMoney() {
return this.$store.getters['loan/getLoanMoney']
},
getProductId() {
return this.$store.getters['trade/product/getProductId']
},
getConfirmNum() {
return this.$store.getters.getConfirmNum
}
},
...
}
複製代碼
使用 mapGetters 輔助函數
咱們可使用 輔助函數 - mapGetters 幫助咱們生成 計算屬性。
var bill = {
data() {
return {...}
},
computed: {
...mapGetters(['getName', 'getConfirmNum']),
...mapGetters({
getTradeType: 'trade/getTradeType'
}),
...mapGetters('trade', {
getProductId: 'product/getProductId'
}),
...mapGetters('loan', {
'getLoanMoney': 'getLoanMoney'
})
},
...
}
複製代碼
使用 createNamespacedHelpers 輔助函數
咱們可使用 輔助函數 - createNamespacedHelpers、mapGetters 幫助咱們生成 計算屬性。
createNamespacedHelpers 能夠幫助咱們生成 基於命名空間 的 輔助函數。
// 基於 全局 命名空間 的 mapGetters
var mapGetters = Vuex.mapGetters
// 基於 trade 命名空間 的 mapGetters
var tradeMapGetters = Vuex.createNamespacedHelpers('trade').mapGetters
// 基於 trade/product 命名空間 的 mapGetters
var productMapGetters = Vuex.createNamespacedHelpers('trade/product').mapGetters
// 基於 loan 命名空間的 mapGetters
var loanMapGetters = Vuex.createNamespacedHelpers('loan').mapGetters
var bill = {
data() {
return {...}
},
computed: {
...mapGetters(['getName', 'getConfirmNum']),
...tradeMapGetters({
getTradeType: 'getTradeType'
}),
...productMapGetters(['getProductId']),
...loanMapGetters(['getLoanMoney'])
}
}
複製代碼
mutation 用於 更改 vuex 的 state, 它的用法和 state、getter 相似,咱們一樣以一個示例來講明。
var options = {
state: {
name: 'zhangsan'
},
mutations: {
setName(state, name) {
state.name = name
}
},
modules: {
trade: {
namespaced: true,
state: {
type: 'waiting'
},
mutations: {
setTradeType(state, type) {
state.type = type
}
}
modules: {
product: {
namespaced: true,
state: {
id: 1
},
mutations: {
setProductId(state, id) {
state.id = id
}
}
}
}
},
loan: {
namespaced: true,
state: {
money: 100000
},
mutations: {
setLoanMoney(state, money) {
state.money = money
}
}
},
confirm: {
state: {
num: 10
},
mutations: {
setConfirmNum(state, num) {
state.num = num
}
}
}
}
}
複製代碼
直接使用
在 組件中,咱們能夠經過 this.$store.commit(type, payload) 的方式提交 mutation,觸發 state狀態 的 更改。
var bill = {
...
methods: {
setName() {
this.$store.commit('setName', '李四')
},
setTradeType() {
this.$store.commit('trade/setTradeType', 'complete')
},
setProductId() {
this.$store.commit('trade/product/setProductId', '2')
},
setLoanMoney() {
this.$store.commit('loan/setLoanMoney', '2000000')
},
setConfirmNum() {
this.$store.commit('setConfirmNum', '20')
}
}
}
複製代碼
使用 mapMutations 輔助函數
咱們可使用 輔助函數 - mapMutations 幫助咱們生成 組件方法 來提交 mutation,觸發 state狀態 的 更改。
var mapMutations = Vuex.mapMutations
var bill = {
...
methods: {
// 給 bill組件 添加 setName 方法, 映射 this.$store.commit('setName')
// 給 bill組件 添加 setConfirmNum 方法, 映射 this.$store.commit('setConfirmNum')
...mapMutations(['setName', 'setConfirmNum']),
// 給 bill組件 添加 setTradeType 方法, 映射 this.$store.commit("trade/setTradeType') ...mapMutations({ setTradeType: 'trade/setTradeType' }), // 給 bill組件 添加 setProductId, 映射 this.$store.commit('trade/product/setProductId') ...mapMutations('trade', { setProductId: 'product/setProductId' }), // 給 bill組件 添加 setLoanMoney, 映射 this.$store.commit('loan/setLoanMoney') ...mapMutations('loan', { setLoanMoney: function(commit, money) { commit('setLoanMoney', money) } }), submit() { // this.$store.commit('setName', '李四') this.setName('李四') // this.$store.commit('setConfirmNum', 20) this.setConfirmNum(20) // this.$store.commit('trade/setTradeType', 'complete') this.setTradeType('complete') // this.$store.commit('trade/product/setProductId', 2345) this.setProductId('2345') // this.$store.commit('loan/setLoanMoney', 2000000) this.setLoanMoney(20000000) } } } 複製代碼
使用 createNamespacedHelpers 輔助函數
咱們可使用 輔助函數 - createNamespacedHelpers、mapMutations 幫助咱們生成 生成 組件方法 來提交 mutation,觸發 state狀態 的 更改。。
createNamespacedHelpers 能夠幫助咱們生成 基於命名空間 的 輔助函數。
// 基於 全局 命名空間 的 mapMutations
var mapMutations = Vuex.mapMutations
// 基於 trade 命名空間 的 mapMutations
var tradeMapMutations = Vuex.createNamespacedHelpers('trade').mapMutations
// 基於 trade/product 命名空間 的 mapMutations
var productMapMutations = Vuex.createNamespacedHelpers('trade/product').mapMutations
// 基於 loan 命名空間的 mapMutations
var loanMapMutations = Vuex.createNamespacedHelpers('loan').mapMutations
var bill = {
...
methods: {
// 給 bill組件 添加 setName 方法, 映射 this.$store.commit('setName')
// 給 bill組件 添加 setConfirmNum 方法, 映射 this.$store.commit('setConfirmNum')
...mapMutations(['setName', 'setConfirmNum']),
// 給 bill組件 添加 setTradeType 方法, 映射 this.$store.commit('trade/setTradeType')
...tradeMapMutations(['setTradeType']),
// 給 bill組件 添加 setProductId, 映射 this.$store.commit('trade/product/setProductId')
...productMapMutations(['setProductId']),
// 給 bill組件 添加 setLoanMoney, 映射 this.$store.commit('loan/setLoanMoney')
...loanMapMutations({
setLoanMoney: function(commit, money) {
commit('setLoanMoney', money)
}
}),
submit() {
// this.$store.commit('setName', '李四')
this.setName('李四')
// this.$store.commit('setConfirmNum', 20)
this.setConfirmNum(20)
// this.$store.commit('trade/setTradeType', 'complete')
this.setTradeType('complete')
// this.$store.commit('trade/product/setProductId', 2345)
this.setProductId('2345')
// this.$store.commit('loan/setLoanMoney', 2000000)
this.setLoanMoney(20000000)
}
}
}
複製代碼
action 相似於 mutation,不一樣之處在於:action 提交的是 mutation,而 不是直接變動狀態; action 能夠 包含任意異步操做。
action 在 組件 中的使用, 和 mutation 相似,咱們經過一個 示例 來講明:
var options = {
state: {
name: 'zhangsan'
},
mutations: {
setName(state, name) {
state.name = name
}
},
actions: {
setName(context, name) {
setTimeout(() => {
context.commit('setName', name)
}, 0)
}
}
modules: {
trade: {
namespaced: true,
state: {
type: 'waiting'
},
mutations: {
setTradeType(state, type) {
state.type = type
}
}
actions: {
setTradeType(context, type) {
setTimeout(() => {
context.commit('setTradeType', type)
}, 1500)
}
}
modules: {
product: {
namespaced: true,
state: {
id: 1
},
mutations: {
setProductId(state, id) {
state.id = id
}
},
actions: {
setProductId(context, id) {
setTimeout(() => {
context.commit('setProductId', id)
}, 3000)
}
}
}
}
},
loan: {
namespaced: true,
state: {
money: 100000
},
mutations: {
setLoanMoney(state, money) {
state.money = money
}
},
actions: {
setLoanMoney(context, money) {
setTimeout(() => {
context.commit('setLoanMoney', money)
}, 2000)
}
}
},
confirm: {
state: {
num: 10
},
mutations: {
setConfirmNum(state, num) {
state.num = num
}
},
actions: {
setConfirmNum(context, num) {
setTimeout(() => {
context.commit('setConfirmNum', num)
}, 5000)
}
}
}
}
}
複製代碼
直接使用
在組件中,咱們能夠直接經過 this.$store.dispatch(type, payload) 的方式 派發 action,觸發 mutaion 的 提交。
var bill = {
...
methods: {
setName() {
this.$store.dispatch('setName', '李四')
},
setTradeType() {
this.$store.dispatch('trade/setTradeType', 'complete')
},
setProductId() {
this.$store.dispatch('trade/product/setProductId', '2')
},
setLoanMoney() {
this.$store.dispatch('loan/setLoanMoney', '2000000')
},
setConfirmNum() {
this.$store.dispatch('setConfirmNum', '20')
}
}
}
複製代碼
使用 mapActions 輔助函數
咱們可使用 輔助函數 - mapActions 幫助咱們生成 組件方法 來派發 action,觸發 mutation 的 提交。
var mapActions = Vuex.mapActions
var bill = {
...
methods: {
// 給 bill組件 添加 setName 方法, 映射 this.$store.dispatch('setName')
// 給 bill組件 添加 setConfirmNum 方法, 映射 this.$store.dispatch('setConfirmNum')
...mapActions(['setName', 'setConfirmNum']),
// 給 bill組件 添加 setTradeType 方法, 映射 this.$store.dispatch("trade/setTradeType') ...mapActions({ setTradeType: 'trade/setTradeType' }), // 給 bill組件 添加 setProductId, 映射 this.$store.dispatch('trade/product/setProductId') ...mapActions('trade', { setProductId: 'product/setProductId' }), // 給 bill組件 添加 setLoanMoney, 映射 this.$store.dispatch('loan/setLoanMoney') ...mapActions('loan', { setLoanMoney: function(dispatch, money) { dispatch('setLoanMoney', money) } }), submit() { // this.$store.dispatch('setName', '李四') this.setName('李四') // this.$store.dispatch('setConfirmNum', 20) this.setConfirmNum(20) // this.$store.dispatch('trade/setTradeType', 'complete') this.setTradeType('complete') // this.$store.dispatch('trade/product/setProductId', 2345) this.setProductId('2345') // this.$store.dispatch('loan/setLoanMoney', 2000000) this.setLoanMoney(20000000) } } } 複製代碼
使用 createNamespacedHelpers 輔助函數
咱們可使用 輔助函數 - createNamespacedHelpers、mapActions 幫助咱們生成 生成 組件方法 來派發 action,觸發 mutation 的 提交。。
createNamespacedHelpers 能夠幫助咱們生成 基於命名空間 的 輔助函數。
// 基於 全局 命名空間 的 mapActions
var mapActions = Vuex.mapActions
// 基於 trade 命名空間 的 mapActions
var tradeMapActions = Vuex.createNamespacedHelpers('trade').mapActions
// 基於 trade/product 命名空間 的 mapActions
var productMapActions = Vuex.createNamespacedHelpers('trade/product').mapActions
// 基於 loan 命名空間的 mapActions
var loanMapActions = Vuex.createNamespacedHelpers('loan').mapActions
var bill = {
...
methods: {
// 給 bill組件 添加 setName 方法, 映射 this.$store.dispatch('setName')
// 給 bill組件 添加 setConfirmNum 方法, 映射 this.$store.dispatch('setConfirmNum')
...mapActions(['setName', 'setConfirmNum']),
// 給 bill組件 添加 setTradeType 方法, 映射 this.$store.dispatch('trade/setTradeType')
...tradeMapActions(['setTradeType']),
// 給 bill組件 添加 setProductId, 映射 this.$store.dispatch('trade/product/setProductId')
...productMapActions(['setProductId']),
// 給 bill組件 添加 setLoanMoney, 映射 this.$store.dispatch('loan/setLoanMoney')
...loanMapActions({
setLoanMoney: function(dispatch, money) {
dispatch('setLoanMoney', money)
}
}),
submit() {
// this.$store.dispatch('setName', '李四')
this.setName('李四')
// this.$store.dispatch('setConfirmNum', 20)
this.setConfirmNum(20)
// this.$store.dispatch('trade/setTradeType', 'complete')
this.setTradeType('complete')
// this.$store.dispatch('trade/product/setProductId', 2345)
this.setProductId('2345')
// this.$store.dispatch('loan/setLoanMoney', 2000000)
this.setLoanMoney(20000000)
}
}
}
複製代碼
不能經過 store.state = xx 方式直接修改 state, 不然會 拋出異常。
class Store {
...
// 訪問 store實例的 state屬性,實質爲訪問 store._vm._data.$$state 屬性
get state () {
return this._vm._data.$$state
}
set state (v) {
{
assert(false, `use store.replaceState() to explicit replace store state.`);
}
}
...
replaceState (state) {
this._withCommit(() => {
this._vm._data.$$state = state;
});
}
}
複製代碼
爲何不能在 set state 中, 使用 replaceState中的方式修改 state?? 暫未理解。
經過 store.watch 能夠 響應式地偵聽 fn 的返回值,當值改變時 調用callback。
fn 接收 store 的 state 做爲第一個參數,其 getter 做爲第二個參數, 只要 vuex 的 狀態 發生 變化, fn 都會執行, 若是 返回的值和上一次不同, 觸發 callback。
經過 store.subscribe 方法能夠 訂閱 store 的 mutation。 當咱們經過 store.commit(type, payload) 的方式提交 mutation 時, 先觸發 type 對應的 mutation, 再觸發 subscribe 註冊的 handler。
vue-devtools 就是利用 store.subscribe 來追蹤 應用中提交的 mutation。
store.subscribe((mutation, state) => {
// vue-devtools 提供的 devtoolHook
devtoolHook.emit('vuex:mutation', mutation, state);
});
複製代碼
store.subscribe 通常被 插件 調用。
經過 store.subscribeAction 方法能夠訂閱 store 的 action。 用法以下:
store.subscribeAction({
before: function(action, state) {
...
},
after: function(action, state) {
...
}
})
複製代碼
當咱們經過 store.dispatch(type, payload) 的方式派發 action 時, 先觸發 before 對應的 handler,再觸發 type 對應的 mutation, 最後觸發 after 對應的 handler。
若是 subscribeAction 的參數是一個 函數, 默認當作 before 對應的 handler, 即:
store.subscribeAction(function(action, state) {
...
})
複製代碼
經過 store.registerModule 方法能夠 動態註冊一個 新模塊。
registerModule 的 第一個參數,表明 註冊的模塊名及所在的父module; 第二個參數 是 註冊module須要的配置項, 即 { namespaced, state, getters, actions, mutations, modules }。
// 在 根module 中註冊 moduleA
store.registerModule('moduleA', {...})
// 在 根module 中註冊 moduleA
store.registerModule(['moduleA'], {...})
// 在 moduleB 中註冊 moduleC
store.registerModule(['moduleA', 'moduleB', 'moduleC'], {...})
複製代碼
注意,在第三個示例中, 若是 moduleA 在 根module 中不存在, moduleB 在 moduleA 中不存在, 會拋出 異常。
動態註冊一個新模塊之後, store實例會從新收集state、getters、mutations、actions, 從新爲store實例構建vm實例(響應式原理)。
經過 store.unregisterModule 方法能夠卸載一個動態模塊。
unregisterModule 只有 一個參數, 表明須要 卸載的模塊及所在的父module。
// 在 根module 中卸載 moduleA
store.unregisterModule('moduleA')
// 在 根module 中卸載 moduleA
store.unregisterModule(['moduleA'])
// 在 moduleB 中卸載 moduleA
store.unregisterModule(['moduleA', 'moduleB', 'moduleC'])
複製代碼
卸載的模塊必須是動態註冊的模塊,不然拋出異常。 緣由是: 卸載靜態module 時, 靜態module 不會從 父module 中移除, 可是 靜態module 對應的 state 會被移除, 若是 vue組件 中有使用 靜態module 中的 state,會報錯。 這裏應該是vux的一個bug吧。
若是 要卸載的模塊在父模塊中不存在,會 報錯。 這裏應該也是一個bug。
若是 要卸載的模塊的父模塊的路勁不對, 會 報錯。
卸載一個動態註冊的模塊之後, store實例會從新收集state、getters、mutations、actions, 從新爲store實例構建vm實例(響應式原理)。
devtools 配置項
爲 store實例 打開或者關閉 devtools。
若是將 devtools 設置爲 false, vue-devtools 就沒法經過 store.subscribe 方法 訂閱 store 的 mutation,沒法追蹤store提交的mutation。