1.前言
狀態(tài)管理在開(kāi)發(fā)中已經(jīng)算是老生常談了,本篇文章我們轉(zhuǎn)向前端方向的Vue
框架误褪,看看Vuex
是怎么通過(guò)store
處理數(shù)據(jù)狀態(tài)的管理的。由于Vue
框架本身就是響應(yīng)式處理數(shù)據(jù)的栅组,所以store
更多的為了跨路由去管理數(shù)據(jù)的狀態(tài)棕兼。在使用store
之前,我們先來(lái)說(shuō)明store
的兩個(gè)特性:
-
Vuex
的狀態(tài)存儲(chǔ)是響應(yīng)式的圆存。當(dāng)Vue
組件從store
中讀取狀態(tài)的時(shí)候叼旋,若store
中的狀態(tài)發(fā)生變化,那么相應(yīng)的組件也會(huì)相應(yīng)地得到高效更新沦辙。 - 你不能直接改變
store
中的狀態(tài)夫植。改變store
中的狀態(tài)的唯一途徑就是顯式地提交(commit) mutation
。這樣使得我們可以方便地跟蹤每一個(gè)狀態(tài)的變化油讯,從而讓我們能夠?qū)崿F(xiàn)一些工具幫助我們更好地了解我們的應(yīng)用详民。
store
是由五大成員組成的,開(kāi)發(fā)者可根據(jù)業(yè)務(wù)復(fù)雜程度撞羽,合理的使用它們阐斜。我們先來(lái)了解一下store
的五大成員:
2.五大成員
-
State
:Vuex
的store
中的state
是響應(yīng)式的,當(dāng)state
中的數(shù)據(jù)發(fā)生變化時(shí)诀紊,所有依賴(lài)于該數(shù)據(jù)的組件都會(huì)自動(dòng)更新谒出。 -
Getters
:Vuex
的store
中的getters
可以理解為store
的計(jì)算屬性,它們會(huì)基于store
中的state
派生出一些新的狀態(tài)邻奠,供組件使用笤喳。 -
Mutations
:Vuex
的store
中的mutations
是同步的事件操作,它們用于更改store
中的state
碌宴。在Vuex
中杀狡,mutations
是唯一可以修改store
中的state的方式。 -
Actions
:Vuex
的store
中的actions
是異步的事件操作贰镣,它們用于處理異步邏輯呜象,如API請(qǐng)求等膳凝。Actions
可以通過(guò)提交mutations
來(lái)修改store
中的state
。 -
Modules
:Vuex
的store
可以通過(guò)模塊化的方式組織代碼恭陡,將一個(gè)大的store
拆分成多個(gè)小的模塊蹬音,每個(gè)模塊都有自己的state
、getters
休玩、mutations
和actions
著淆。
其中State
和Mutations
是構(gòu)成store
必不可少的成員。那么store
是怎么使用的呢拴疤?我們來(lái)看下一章節(jié)永部。
3.Store的使用
3.1.安裝:
npm install vuex
3.2.最簡(jiǎn)單的用法:
我們創(chuàng)建一個(gè)新的js
文件,引入vuex
并通過(guò)Vue.use()
使用它呐矾,然創(chuàng)建一個(gè)Store
對(duì)象并export
:
import Vuex from 'vuex'
import Vue from "vue";
Vue.use(Vuex)
const store = new Vuex.Store({
state: { //在state對(duì)象建立需要數(shù)據(jù)
count: 0
},
mutations: {
add: function (state) {
state.count++;
}
},
});
export default store
其中count
是我們?cè)?code>state里聲明的需要被監(jiān)聽(tīng)的變量苔埋。add
是個(gè)方法,其實(shí)現(xiàn)是count++
凫佛。接下來(lái)我們?cè)?code>main.js里注入store
實(shí)例:
import store from "./store"
new Vue({
router,
store,
render: h => h(App),
}).$mount('#app')
好了我們的store
已經(jīng)可以用了讲坎。我們假設(shè)有兩個(gè)路由:home page
和new page
,我們看看store
是怎么監(jiān)聽(tīng)數(shù)據(jù)變化并跨路由共享的愧薛。
home page
代碼如下:
<template>
<div>
<div>I am home page</div>
<div>{{ getCount() }}</div>
<div @click="addCount" class="button">add count</div>
<div @click="gotoNewPage" class="button">Go to newpage</div>
</div>
</template>
<script>
export default {
methods: {
gotoNewPage() {
this.$router.push({ path: "/newpage" });
},
getCount() {
return this.$store.state.count;
},
addCount() {
this.$store.commit('add');
},
},
};
</script>
<style lang="scss" scoped>
.button {
width: 100px;
height: 50px;
background: #527dab;
margin-top: 15px;
}
</style>
其中getCount()
方法從store
的state
里讀取count
的值晨炕。addCount()
方法用來(lái)調(diào)用mutations
的add()
方法,實(shí)現(xiàn)count++
毫炉。們點(diǎn)擊“add count”按鈕看下效果:
我們可以看到
count
的狀態(tài)變化會(huì)被監(jiān)聽(tīng)到瓮栗。那么我們跳轉(zhuǎn)至new page
,代碼如下:
<template>
<div>
<div>new page</div>
<div>{{ getCount() }}</div>
<div @click="back" class="button">back</div>
</div>
</template>
<script>
export default {
methods: {
back() {
this.$router.push({
path: "/home",
});
},
getCount() {
return this.$store.state.count;
},
},
};
</script>
<style lang="scss" scoped>
.button {
width: 100px;
height: 50px;
background: #527dab;
margin-top: 15px;
}
</style>
跨路由讀取到了count
的值瞄勾。
以上就是
Store
最簡(jiǎn)單的用法费奸,接下來(lái)我們進(jìn)階一下。
3.3.getters和actions
為store
增加getters
和actions
:
const store = new Vuex.Store({
state: { //在state對(duì)象建立需要數(shù)據(jù)
count: 0
},
getters: {
getCount: function (state) {
return "count是: " + state.count
}
},
mutations: {
add: function (state) {
state.count++;
}
},
actions: {
addAction: function (context) {
setTimeout(() => {
context.commit('add')
}, 500)
}
}
});
如果上一節(jié)所講进陡,getters
類(lèi)似于計(jì)算屬性愿阐,有時(shí)候我們需要從store
中的 state
中派生出一些狀態(tài),那么就需要getters
了趾疚。getters
的返回值會(huì)根據(jù)它的依賴(lài)被緩存起來(lái)缨历,且只有當(dāng)它的依賴(lài)值發(fā)生了改變才會(huì)被重新計(jì)算。actions
的核心在于可以包含任意異步操作糙麦。它提交的是mutation
垂寥,不能直接修改state
的值粗恢。addAction
模擬了一個(gè)異步操作轩娶,500ms之后執(zhí)行add
方法暗甥。我們接下來(lái)看一下使用,改造一下home page
的getCount()
和addCount()
方法:
getCount() {
return this.$store.getters.getCount;
},
addCount() {
this.$store.dispatch("addAction");
},
actions
需要使用store.dispatch()
進(jìn)行分發(fā)焚廊,我們看一下執(zhí)行結(jié)果:
點(diǎn)擊
add count
按鈕后500ms后冶匹,count
加1习劫,通過(guò)getters
得到新的顯示文本。
3.4.Modules
由于使用單一狀態(tài)樹(shù)徙硅,應(yīng)用的所有狀態(tài)會(huì)集中到一個(gè)比較大的對(duì)象榜聂。當(dāng)應(yīng)用變得非常復(fù)雜時(shí),store
對(duì)象就有可能變得相當(dāng)臃腫嗓蘑。
為了解決以上問(wèn)題,Vuex
允許我們將store
分割成模塊(module
)匿乃。每個(gè)模塊擁有自己的state
桩皿、mutation
、action
幢炸、getter
泄隔、甚至是嵌套子模塊。接著舉個(gè)例子:
const userStore = {
state: { //在state對(duì)象建立需要數(shù)據(jù)
name: "tom"
},
getters: {
getName: function (state) {
return "name是: " + state.name
}
},
mutations: {
setName: function (state, name) {
state.name = name;
}
},
actions: {
setNameAction: function (context,name) {
console.log("setNameAction")
setTimeout(() => {
console.log("setName")
context.commit('setName', name)
}, 500)
}
}
};
export default userStore
剛才的實(shí)例代碼中宛徊,我們?cè)黾右粋€(gè)userStore
佛嬉,為其配置了state
,getters
闸天,mutations
和actions
暖呕。然后我們?yōu)橹暗?code>store增加modules
,代碼如下:
import Vuex from 'vuex'
import Vue from "vue";
import userModule from "@/store/modules/user"
Vue.use(Vuex)
const store = new Vuex.Store({
state: { //在state對(duì)象建立需要數(shù)據(jù)
count: 0
},
getters: {
getCount: function (state) {
return "count是: " + state.count
}
},
mutations: {
add: function (state) {
state.count++;
}
},
actions: {
addAction: function (context) {
setTimeout(() => {
context.commit('add')
}, 500)
}
},
modules:{
user: userModule
}
});
export default store
導(dǎo)入了userModule
苞氮,我們看看這個(gè)user
的module
是怎么用的湾揽。
<template>
<div>
<div>I am home page</div>
<div>{{ getName() }}</div>
<div @click="setName" class="button">set Name</div>
</div>
</template>
<script>
export default {
methods: {
getName(){
return this.$store.getters.getName;
},
setName() {
this.$store.dispatch("setNameAction","bigcatduan");
},
},
};
</script>
<style lang="scss" scoped>
.button {
width: 100px;
height: 50px;
background: #527dab;
margin-top: 15px;
}
</style>
如果是從getters
里取數(shù)據(jù)用法同之前的實(shí)例一樣。如果是從state
里取數(shù)據(jù)則需要指定module
:
getName(){
return this.$store.state.user.name;
},
需要注意的是笼吟,如果module
里的getter
的方法名與父節(jié)點(diǎn)store
的沖突了库物,則運(yùn)行的時(shí)候會(huì)取父節(jié)點(diǎn)store
的值,但會(huì)報(bào)duplicate getter key
的錯(cuò)誤贷帮。如果module
里的getter
的方法名與同節(jié)點(diǎn)的其它modules
沖突了戚揭,則運(yùn)行的時(shí)候會(huì)根據(jù)在節(jié)點(diǎn)注冊(cè)的順序來(lái)取值,同時(shí)報(bào)duplicate getter key
的錯(cuò)誤撵枢。比如:
modules:{
user: userModule,
product: productModule
}
由于userModule
先被注冊(cè)到modules
里民晒,所以取值時(shí)會(huì)取userModule
的值。
如果父子之間或同節(jié)點(diǎn)的action
和mutation
的方法名相同的話(huà)诲侮,則各個(gè)節(jié)點(diǎn)同方法名的方法都會(huì)被執(zhí)行镀虐。
好了到此為止我們發(fā)現(xiàn)一個(gè)問(wèn)題,如果項(xiàng)目龐大沟绪,免不了會(huì)在不同的module
里定義相同的變量或方法名稱(chēng)刮便,讓開(kāi)發(fā)者自己去維護(hù)龐大的方法/變量列表顯然不現(xiàn)實(shí),于是vuex
引入了命名空間namespace
绽慈。
3.5.命名空間
默認(rèn)情況下恨旱,模塊內(nèi)部的action
和mutation
仍然是注冊(cè)在全局命名空間的——這樣使得多個(gè)模塊能夠?qū)ν粋€(gè)action
或mutation
作出響應(yīng)辈毯。Getter
同樣也默認(rèn)注冊(cè)在全局命名空間,但是目前這并非出于功能上的目的(僅僅是維持現(xiàn)狀來(lái)避免非兼容性變更)搜贤。必須注意谆沃,不要在不同的、無(wú)命名空間的模塊中定義兩個(gè)相同的getter
從而導(dǎo)致錯(cuò)誤仪芒。
如果希望你的模塊具有更高的封裝度和復(fù)用性,你可以通過(guò)添加namespaced: true
的方式使其成為帶命名空間的模塊掂名。當(dāng)模塊被注冊(cè)后据沈,它的所有getter
、action
及mutation
都會(huì)自動(dòng)根據(jù)模塊注冊(cè)的路徑調(diào)整命名饺蔑。
比如我們?yōu)閯偛诺?code>userModule設(shè)置namespaced: true
锌介。
const userStore = {
namespaced: true,
state: { //在state對(duì)象建立需要數(shù)據(jù)
name: "tom"
},
getters: {
getName: function (state) {
return "name是: " + state.name
}
},
mutations: {
setName: function (state, name) {
state.name = name;
}
},
actions: {
setNameAction: function (context,name) {
console.log("setNameAction")
setTimeout(() => {
console.log("setName")
context.commit('setName', name)
}, 500)
}
}
};
export default userStore
set
和get
的相應(yīng)方式變化如下:
getName() {
return this.$store.getters["user/getName"];
},
setName() {
this.$store.dispatch("user/setNameAction", "bigcatduan");
},
在getter
,dispatch
或commit
時(shí)需要顯示的指明局部的命名空間猾警。
接下來(lái)我們思考一下孔祸,是否可以在不同的module
下讀取到其它module
的內(nèi)容呢?答案是可以的发皿。比如我們的store
結(jié)構(gòu)如下:
根節(jié)點(diǎn):
import Vuex from 'vuex'
import Vue from "vue";
import userModule from "@/store/modules/user"
import productModule from "@/store/modules/product"
Vue.use(Vuex)
const store = new Vuex.Store({
state: { //在state對(duì)象建立需要數(shù)據(jù)
count: 20
},
getters: {
getCount: function (state) {
return "count是: " + state.count
}
},
mutations: {
add: function (state) {
state.count++;
}
},
actions: {
addAction: function (context) {
setTimeout(() => {
context.commit('add')
}, 500)
}
},
modules:{
user: userModule,
product: productModule
}
});
export default store
里面包含了user
和product
這兩個(gè)module崔慧。
代碼如下:
//user
const userStore = {
namespaced: true,
state: { //在state對(duì)象建立需要數(shù)據(jù)
name: "tom"
},
getters: {
getName: function (state, getters, rootState, rootGetters) {
return "name是: " + state.name
}
},
mutations: {
setName: function (state, name) {
state.name = name;
}
},
actions: {
setNameAction: function (context,name) {
setTimeout(() => {
context.commit('setName', name)
}, 500)
}
}
};
export default userStore
//product
const productStore = {
namespaced: true,
state: { //在state對(duì)象建立需要數(shù)據(jù)
price: 15
},
getters: {
getPrice: function (state) {
return "price是: " + state.price
}
},
mutations: {
setPrice: function (state, price) {
state.price = price;
}
},
actions: {
setPriceAction: function (context,price) {
setTimeout(() => {
context.commit('setPrice', price)
}, 500)
}
}
};
export default productStore
我們改造一下user
的getters
:
//user
getters: {
getName: function (state, getters, rootState, rootGetters) {
console.log("rootState count: ",rootState.count)
console.log("product state price: ",rootState.product.price)
console.log("root getter getCount: ",rootGetters.getCount)
console.log("product getter getPrice: ",rootGetters["product/getPrice"])
return "name是: " + state.name
}
},
除了state
,vuex
還為我們提供了getters
雳窟,rootState
和rootGetters
這幾個(gè)參數(shù)尊浪。于是我們可以讀取根節(jié)點(diǎn)state
相應(yīng)的變量,其他節(jié)點(diǎn)state
相應(yīng)的變量封救,根getters
里的方法拇涤,還可以找到其他getters
里的方法。上面代碼的打印結(jié)果如下:
我們?cè)賮?lái)改造一下
actions
:
actions: {
setNameActionRoot: {
root:true,
handler (namespacedContext, name){
console.log("setNameAction")
setTimeout(() => {
console.log("setName")
namespacedContext.commit('setName', name)
}, 500)
}
},
}
root:true
意味著我們?yōu)檫@個(gè)帶命名空間的模塊注冊(cè)全局action
誉结,所以我們依然可以不指定命名空間執(zhí)行dispatch
方法:
this.$store.dispatch("setNameActionRoot", "bigcatduan");
繼續(xù)改造一下actions
:
actions: {
setNameAction: function (context,name) {
setTimeout(() => {
context.commit('setName', name)
}, 500)
},
setSomeActions ( { dispatch, commit, getters, rootGetters },someAction){
console.log("root getter getCount: ",rootGetters.getCount)
console.log("product getter getPrice: ",rootGetters["product/getPrice"])
dispatch('setNameAction',someAction.name)
commit('setName',someAction.name)
commit('product/setPrice', someAction.price, {root:true})
}
}
可以拿到全局rootGetters
鹅士。若需要在全局命名空間內(nèi)分發(fā)action
或提交 mutation
,將{ root: true }
作為第三參數(shù)傳給dispatch
或commit
即可惩坑。
好了以上就是store
的用法掉盅,更多用法大家可以繼續(xù)探索。接下來(lái)我們來(lái)講一下store
的實(shí)現(xiàn)原理以舒。
4.實(shí)現(xiàn)原理
我們先來(lái)看看構(gòu)造方法:
//Store
constructor (options = {}) {
if (__DEV__) {
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
assert(this instanceof Store, `store must be called with the new operator.`)
}
const {
plugins = [],
strict = false,
devtools
} = options
// store internal state
this._committing = false
this._actions = Object.create(null)
this._actionSubscribers = []
this._mutations = Object.create(null)
this._wrappedGetters = Object.create(null)
this._modules = new ModuleCollection(options)
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._makeLocalGettersCache = Object.create(null)
// EffectScope instance. when registering new getters, we wrap them inside
// EffectScope so that getters (computed) would not be destroyed on
// component unmount.
this._scope = null
this._devtools = devtools
// bind commit and dispatch to self
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
}
// strict mode
this.strict = strict
const state = this._modules.root.state
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)
// initialize the store state, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreState(this, state)
// apply plugins
plugins.forEach(plugin => plugin(this))
}
創(chuàng)建了各個(gè)成員變量趾痘,其中最重要的是_modules
的創(chuàng)建。之后依次執(zhí)行installModule()
蔓钟,resetStoreState()
和plugins.forEach(plugin => plugin(this))
永票。我們先來(lái)看看_modules
的創(chuàng)建。
4.1. ModuleCollection
_modules
對(duì)所有的module
進(jìn)行了初始化并構(gòu)造了其依賴(lài)關(guān)系。其構(gòu)造方法實(shí)現(xiàn)如下:
//ModuleCollection
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
調(diào)用了register
方法:
register (path, rawModule, runtime = true) {
if (__DEV__) {
assertRawModule(path, rawModule)
}
const newModule = new Module(rawModule, runtime)
if (path.length === 0) {
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// register nested modules
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
在初始化的時(shí)候侣集,由于傳入的是根module
键俱,path.length === 0
,所以會(huì)創(chuàng)建一個(gè)Module
對(duì)象世分,并為root
賦值编振,把它設(shè)置為根module
。Module
的構(gòu)造方法如下:
//Module
constructor (rawModule, runtime) {
this.runtime = runtime
// Store some children item
this._children = Object.create(null)
// Store the origin module object which passed by programmer
this._rawModule = rawModule
const rawState = rawModule.state
// Store the origin module's state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
回到ModuleCollection
臭埋,如果存在子modules
踪央,就會(huì)遍歷modules
,遞歸執(zhí)行register()
方法斋泄。而再次執(zhí)行register()
方法時(shí)杯瞻,path.length === 0
為false
,會(huì)找到它的parent
炫掐,并執(zhí)行addChild()
方法:
//Module
addChild (key, module) {
this._children[key] = module
}
由此實(shí)現(xiàn)了modules
依賴(lài)關(guān)系的創(chuàng)建,生成了一個(gè)modules
樹(shù)睬涧。接下來(lái)我們看installModule()
方法的實(shí)現(xiàn):
4.2.installModule()
它的作用是對(duì)根module
進(jìn)行初始化募胃,根據(jù)上一章節(jié)生成的modules
樹(shù),遞歸注冊(cè)每一個(gè)module
畦浓。代碼如下:
export function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
// register in namespace map
if (module.namespaced) {
if (store._modulesNamespaceMap[namespace] && __DEV__) {
console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
}
store._modulesNamespaceMap[namespace] = module
}
// set state
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
if (__DEV__) {
if (moduleName in parentState) {
console.warn(
`[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
)
}
}
parentState[moduleName] = module.state
})
}
const local = module.context = makeLocalContext(store, namespace, path)
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
如果設(shè)置了nameSpace
痹束,則向_modulesNamespaceMap
注冊(cè)。之后如果不是根module
讶请,將模塊的state
添加到state
鏈中祷嘶,就可以按照state.moduleName
進(jìn)行訪(fǎng)問(wèn)。
接下來(lái)創(chuàng)建makeLocalContext
上下文夺溢,為該module
設(shè)置局部的dispatch
论巍、commit
方法以及getters
和state
,為的是在局部的模塊內(nèi)調(diào)用模塊定義的action
和mutation
风响,這個(gè)過(guò)程具體就不展開(kāi)了嘉汰。
接下來(lái)分別遍歷_mutations
,_actions
和_wrappedGetters
進(jìn)行注冊(cè)状勤。注冊(cè)過(guò)程大同小異鞋怀,我們?nèi)?code>_mutations的注冊(cè)來(lái)看看:
function registerMutation (store, type, handler, local) {
const entry = store._mutations[type] || (store._mutations[type] = [])
entry.push(function wrappedMutationHandler (payload) {
handler.call(store, local.state, payload)
})
}
把對(duì)應(yīng)的值函數(shù)封裝后存儲(chǔ)在數(shù)組里面,然后作為store._mutations
的屬性持搜。store._mutations
收集了我們傳入的所有mutation
函數(shù)密似。
installModule()
的分析就完成了,我們繼續(xù)看resetStoreState()
的實(shí)現(xiàn)葫盼。
4.4.resetStoreState()
這個(gè)方法的作用是初始化state
残腌,并且注冊(cè)getters
作為一個(gè)computed
屬性。
export function resetStoreState (store, state, hot) {
const oldState = store._state
const oldScope = store._scope
// bind store public getters
store.getters = {}
// reset local getters cache
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters
const computedObj = {}
const computedCache = {}
// create a new effect scope and create computed object inside it to avoid
// getters (computed) getting destroyed on component unmount.
const scope = effectScope(true)
scope.run(() => {
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldState.
// using partial to return function with only arguments preserved in closure environment.
computedObj[key] = partial(fn, store)
computedCache[key] = computed(() => computedObj[key]())
Object.defineProperty(store.getters, key, {
get: () => computedCache[key].value,
enumerable: true // for local getters
})
})
})
store._state = reactive({
data: state
})
//...
}
遍歷store._wrappedGetters
,并新建computed
對(duì)象進(jìn)行存儲(chǔ)废累,通過(guò)Object.defineProperty
方法為getters
對(duì)象建立屬性并實(shí)現(xiàn)響應(yīng)式邓梅,使得我們通過(guò)this.$store.getters.xxxgetter
能夠訪(fǎng)問(wèn)到該getters
。最后利用reactive
為state
實(shí)現(xiàn)響應(yīng)式邑滨。
4.5.install
在vue
里日缨,通過(guò)Vue.use()
掛載全局變量,所以業(yè)務(wù)組件中通過(guò)this
就能訪(fǎng)問(wèn)到store
:
install (app, injectKey) {
app.provide(injectKey || storeKey, this)
app.config.globalProperties.$store = this
const useDevtools = this._devtools !== undefined
? this._devtools
: __DEV__ || __VUE_PROD_DEVTOOLS__
if (useDevtools) {
addDevtools(app, this)
}
}
4.6.commit和dispatch
最后我們?cè)賮?lái)看看commit和dispatch都做了什么
//commit
commit (_type, _payload, _options) {
// check object-style commit
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
const entry = this._mutations[type]
//...
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
this._subscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.forEach(sub => sub(mutation, this.state))
//...
}
主要做了兩件事:1.遍歷_mutations
掖看,執(zhí)行handler
匣距。2.遍歷_subscribers
,通知訂閱者哎壳。我們?cè)诳?code>dispatch
dispatch (_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload)
const action = { type, payload }
const entry = this._actions[type]
//...
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
return new Promise((resolve, reject) => {
result.then(res => {
try {
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
resolve(res)
}, error => {
try {
this._actionSubscribers
.filter(sub => sub.error)
.forEach(sub => sub.error(action, this.state, error))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in error action subscribers: `)
console.error(e)
}
}
reject(error)
})
})
}
dispatch
使用Promise
封裝了異步操作毅待,遍歷_actions
執(zhí)行handler
操作,并遍歷_actionSubscribers
通知訂閱者归榕。
5.總結(jié)
Vuex
的狀態(tài)管理方式store
的使用和原理就介紹到這里尸红。最后截取一張官網(wǎng)的圖為大家進(jìn)行一下總結(jié)。