準(zhǔn)備工作
如果前面有自行實(shí)現(xiàn)過vue-router吗坚,那這里就沒有工作了馏谨,否則移步手寫Vue2核心(七):vue-router實(shí)現(xiàn)
VueRouter與install
vuex的引用比vue-router拆分得細(xì)一點(diǎn)彼绷,但實(shí)現(xiàn)原理等同于vue-router权旷,一些重復(fù)的實(shí)現(xiàn)原理就不過多贅述了织盼,直接上代碼
// vuex/index.js
import { Store, install } from './store'
// 兩種導(dǎo)出方式闻牡,方便用戶可以通過 import {Store},或者通過 import Vuex,通過 Vuex.Store 和 Vuex.install
export {
Store,
install
}
export default {
Store,
install
}
// vuex/store.js
import { applyMixin } from './mixin'
export let Vue // 此 Vue 是用戶注冊(cè)插件時(shí)傳入的 Vue 構(gòu)造函數(shù)
export class Store {
constructor (options) {
console.log(options)
}
}
// 搞得太花里胡哨驮捍,但最終還是在 vuex/index.js 中將 install和store 導(dǎo)出疟呐,所以這里怎么華麗花哨并不重要,能導(dǎo)出install進(jìn)行mixin注入即可
// 實(shí)現(xiàn)原理依舊等同于 vue-router
export const install = (_Vue) => {
Vue = _Vue
applyMixin(Vue)
}
// vuex/minxin.js东且,等同于vue-router的install.js
function vueInit () {
if (this.$options.store) {
this.$store = this.$options.store // 給根屬性增加 $store 屬性
} else if (this.$parent && this.$parent.$store) {
this.$store = this.$parent.$store
}
}
export const applyMixin = (Vue) => {
Vue.mixin({
beforeCreate: vueInit // 繼續(xù)拆启具,原理還是一樣,通過查找父組件的$store屬性來判斷獲取實(shí)例
})
}
響應(yīng)式數(shù)據(jù)珊泳,實(shí)現(xiàn)state與getters
vuex
的state
鲁冯,相當(dāng)于data
,getters
相當(dāng)于computed
色查,因此getters
是具備緩存薯演,且不同于computed
,是不允許設(shè)置值的(vuex中提供的commit和dipath都不會(huì)直接操作getters)
vuex
是衍生于Vue
的秧了,只能供Vue
使用跨扮,其主要原因在于實(shí)現(xiàn)中,是通過創(chuàng)建一個(gè)新的Vue
實(shí)例验毡,來掛載到Store._vm
上衡创,這樣做的原因是Store
是具備響應(yīng)式數(shù)據(jù)變化的,當(dāng)數(shù)據(jù)變化時(shí)晶通,會(huì)觸發(fā)視圖渲染
頁(yè)面中對(duì)Store
取值時(shí)璃氢,會(huì)觸發(fā)Vue
的依賴收集,但是state
本身是沒必要去掛載到Vue._vm
上的(不會(huì)變?yōu)閷?shí)例屬性)录择。Vue
中提供了$
符拔莱,來設(shè)置這些屬性不會(huì)被Vue
代理。文檔傳送門:vue.data
getters
是以函數(shù)的形式來定義取值的方法隘竭,具備緩存功能。而由于所有屬性均為函數(shù)讼渊,所以需要執(zhí)行才能取值动看,并且不能默認(rèn)幫用戶全部執(zhí)行,否則取值就會(huì)各種不正確爪幻,而是應(yīng)該在使用時(shí)再進(jìn)行取值
通過Object.defineProperty
來對(duì)getters
進(jìn)行劫持菱皆,當(dāng)訪問屬性時(shí),去調(diào)用其對(duì)應(yīng)的函數(shù)執(zhí)行挨稿。而getters
是具備緩存功能的仇轻,所以需要將所有getters
中定義的屬性都放到計(jì)算屬性中
// vuex/store.js
+ const forEachValue = (obj, cb) => {
+ Object.keys(obj).forEach(key => cb(obj[key], key))
+ }
export let Vue // 此 Vue 是用戶注冊(cè)插件時(shí)傳入的 Vue 構(gòu)造函數(shù)
export class Store {
constructor (options) {
+ const computed = {}
+ // getters實(shí)現(xiàn)
+ this.getters = {}
+ forEachValue(options.getters, (value, key) => {
+ // 通過計(jì)算屬性替換直接執(zhí)行函數(shù)獲取值的形式,計(jì)算屬性具備緩存
+ computed[key] = () => value.call(this, this.state)
+ // value 是函數(shù)奶甘,getter 獲取的是屬性值篷店,所以在獲取的時(shí)候再去執(zhí)行函數(shù)獲取其對(duì)應(yīng)的值
+ // 而且這樣操作是每次取值時(shí)都能取到最新結(jié)果,否則直接執(zhí)行函數(shù)取值后面就沒法變更了
+ Object.defineProperty(this.getters, key, {
+ // 這里要用箭頭函數(shù)保證this指向,否則里面就不能用 call(this)
+ get: () => {
+ // 用call是為了防止用戶在 getters 中使用了this疲陕,當(dāng)然正常都是通過傳入的state state.xxx方淤,而不是 this.state.xxx
+ // return value.call(this, this.state) // 每次取值都會(huì)重新執(zhí)行用戶方法,性能差蹄殃,所以需要替換成計(jì)算屬性取值
+ return this._vm[key]
+ }
+ })
+ })
+ // 用戶肯定是先使用 Vue.use携茂,再進(jìn)行 new Vue.Store({...}),所以這里的 Vue 已經(jīng)是可以拿到構(gòu)造函數(shù)的了
+ // 必須放到f forEachValue 后面诅岩,確保 computed 已經(jīng)有值
+ this._vm = new Vue({
+ data: {
+ // Vue中不會(huì)對(duì) $開頭的屬性進(jìn)行代理操作(不會(huì)掛到_vm上進(jìn)行代理)
+ // 但是其屬性依舊會(huì)被代理到(頁(yè)面獲取時(shí)依然會(huì)被收集依賴)讳苦,因?yàn)槲覀儾粫?huì)直接操作state,而是操作state.xxx吩谦,性能優(yōu)化
+ $$state: options.state
+ },
+ computed
+ })
+ }
+ get state () { // 屬性訪問器
+ return this._vm._data.$$state
+ }
}
實(shí)現(xiàn)commit與dispatch
簡(jiǎn)單的實(shí)現(xiàn)鸳谜,沒啥好說的,唯一需要講一下的是這里類的箭頭函數(shù)逮京,因?yàn)槲覀兪褂?code>commit或dispatch
時(shí)卿堂,是可以通過解構(gòu)賦值的方式來調(diào)用函數(shù)的,但這樣取值會(huì)導(dǎo)致this指向當(dāng)前執(zhí)行上下文
而ES7中的箭頭函數(shù)是通過詞法解析來決定this指向的懒棉,所以解構(gòu)賦值取得的this會(huì)依舊指向Store
mutations
與dispatch
實(shí)現(xiàn):
export class Store {
constructor (options) {
// code...
+ // mutations實(shí)現(xiàn)
+ this.mutations = {}
+ this.actions = {}
+
+ forEachValue(options.mutations, (fn, key) => {
+ this.mutations[key] = payload => fn.call(this, this.state, payload)
+ })
+
+ forEachValue(options.actions, (fn, key) => {
+ this.actions[key] = payload => fn.call(this, this, payload)
+ })
}
get state () { // 屬性訪問器
return this._vm._data.$$state
}
+ commit = (type, payload) => { // ES7語(yǔ)法草描,類的箭頭函數(shù),表示this永遠(yuǎn)指向store實(shí)例
+ this.mutations[type](payload)
+ }
+ dispatch = (type, payload) => {
+ this.actions[type](payload)
+ }
}
ES7類的箭頭函數(shù)示例:
// ES7 類的箭頭函數(shù)編譯結(jié)果示例
window.name = 'window'
function Store () {
this.name = 'Store'
// 注釋掉下面四行策严,則commit方法中的this會(huì)指向window
let { commit } = this
this.commit = () => { // 獲取時(shí)穗慕,實(shí)例上的屬性優(yōu)先于原型上的
commit.call(this) // 通過call,將commit執(zhí)行時(shí)this指向Store實(shí)例
}
}
Store.prototype.commit = function () {
console.log(this.name)
}
let {commit} = new Store() // 這里解構(gòu)取得的commit妻导,this指向的window
// 上面解構(gòu)賦值后相當(dāng)于這樣逛绵,所以調(diào)用的時(shí)候this指向其調(diào)用的上下文環(huán)境,所以為window
// let commit = Store.prototype.commit
commit() // 實(shí)例上也有一個(gè)commit倔韭,commit通過箭頭函數(shù)綁定了this指向
寫到這里术浪,一個(gè)簡(jiǎn)易版的vuex就實(shí)現(xiàn)了,但vuex里有一個(gè)東西叫模塊modules
寿酌,這東西的實(shí)現(xiàn)胰苏,導(dǎo)致上面這個(gè)簡(jiǎn)易版的vuex
需要完全重寫(只是重寫Store
)
但是上面的代碼是很好理解的,所以分開來說醇疼,下面開始真正實(shí)現(xiàn)官方vuex
vuex中模塊的用法
modules硕并,模塊化管理,具備命名空間進(jìn)行數(shù)據(jù)隔離秧荆。通過使用namespaced
進(jìn)行隔離倔毙,沒有指定該屬性中mutations和actions會(huì)影響全局
而對(duì)到state,會(huì)將模塊名作為鍵乙濒,將其state
作為值陕赃,添加到全局上
具體直接看文檔吧,說的很清楚了。官方文檔傳送門:modules
export default new Vuex.Store({
state: { // data
name: 'state',
age: 10
},
getters: { // computed
gettersAge (state) {
return state.age + 20
}
},
mutations: { // 同步變更
changeAge (state, payload) {
state.age = state.age + payload
}
},
actions: {
changeAge ({ commit }, payload) {
setTimeout(() => {
commit('changeAge', payload)
})
}
},
modules: {
a: {
state: {
name: 'modules-a',
age: 10
},
getters: {
getName (staste) {
return staste.name
}
},
mutations: { // 同步變更
changeAge (state, payload) {
state.age = state.age + payload
}
},
modules: {
c: {
namespaced: true, // 有命名空間
state: {
name: 'modules-a-c',
age: 40
}
}
}
},
b: { // 沒有命名空間凯正,則changeAge方法也會(huì)影響到該模塊中的state屬性值
namespaced: true, // 有命名空間
state: {
name: 'modules-b',
age: 20
},
mutations: { // 同步變更
changeAge (state, payload) {
state.age = state.age + payload
}
}
}
}
})
vuex中的模塊收集
其實(shí)就是轉(zhuǎn)換成一個(gè)樹形結(jié)構(gòu)來進(jìn)行管理毙玻,采用遞歸的方式,將用戶傳入的store參數(shù)轉(zhuǎn)換為樹形結(jié)構(gòu)廊散。每個(gè)模塊都被重新包裝成一個(gè)module
類
// module/module.js
export default class Module {
constructor (rawModule) {
this._raw = rawModule
this._children = {}
this.state = rawModule.state
}
getChild (key) { // 獲取子節(jié)點(diǎn)中的某一個(gè)
return this._children[key]
}
addChild (key, module) { // 添加子節(jié)點(diǎn)
this._children[key] = module
}
}
// module/module-collection.js
import { forEachValue } from '../util'
import Module from './module'
// 將傳入的store轉(zhuǎn)成樹型結(jié)構(gòu) _row為該模塊鍵值桑滩,_children為該模塊modules中的鍵值(也轉(zhuǎn)為樹形結(jié)構(gòu)),_state為該模塊中寫的state允睹,深度優(yōu)先
export default class ModuleCollection {
constructor (options) { // 遍歷用戶的屬性對(duì)數(shù)據(jù)進(jìn)行格式化操作
this.root = null
this.register([], options)
console.log(this.root)
}
register (path, rootModule) {
const newModule = new Module(rootModule)
if (path.length === 0) { // 初始化
this.root = newModule
} else {
// 將當(dāng)前模塊定義在父親身上
const parent = path.slice(0, -1).reduce((memo, current) => {
return memo.getChild(current)
}, this.root)
parent.addChild(path[path.length - 1], newModule)
}
// 如果還有modules就繼續(xù)遞歸
if (rootModule.modules) {
forEachValue(rootModule.modules, (module, moduleName) => {
this.register(path.concat(moduleName), module)
})
}
}
}
vuex中的模塊實(shí)現(xiàn)
這里實(shí)現(xiàn)的時(shí)沒有namespace的邏輯运准,具體是將模塊中的參與合并到全局上,對(duì)于用戶傳入配置分別進(jìn)行以下處理:
- state: 將模塊中的state合并到全局缭受,通過模塊名稱作為全局state的鍵胁澳,并設(shè)置成響應(yīng)式
- getters:將模塊中的getters合并到全局,同名的屬性米者,后面的會(huì)覆蓋前面的韭畸,并設(shè)置到自行創(chuàng)建的vue.computed上、
-
mutations和actions:實(shí)現(xiàn)邏輯一致蔓搞,就是將其放入棧中胰丁,等調(diào)用的時(shí)候依次調(diào)用
所以邏輯總結(jié)起來就兩步:將用戶傳入的配置合并到全局,將數(shù)據(jù)設(shè)置為響應(yīng)式
// store.js
/**
* @param {Object} store store實(shí)例
* @param {Array} path 模塊父子關(guān)系喂分,初始為空
* @param {Object} module 轉(zhuǎn)化為樹結(jié)構(gòu)后的模塊
* @param {*} rootState 全局store的state
* @descript 將模塊中的mutations和actions都合并到全局上锦庸,通過棧的方式依次push,調(diào)用的時(shí)候依次執(zhí)行
* 將模塊中的 state 和 getters 也合并到全局上蒲祈,state會(huì)將模塊名設(shè)置為全局的鍵甘萧,而getters則是沒用namespace的話會(huì)合并到全局,后面同名的會(huì)覆蓋前面的
*/
const installMudole = (store, path, module, rootState) => {
// store => [], store.modules => ['a'], store.modules.modules => ['a', 'c']
if (path.length > 0) { // 是子模塊
const parent = path.slice(0, -1).reduce((memo, current) => {
return memo[current]
}, rootState)
// vue-router是使用Vue.util.defineReactive梆掸,所以這里寫成Vue.util.defineReactive(parent, path[path.length - 1], module.state)也可以
// 因?yàn)槟繕?biāo)就是要把模塊定義成響應(yīng)式的扬卷,源碼路徑:/src/core/util
// 這里不用set也能實(shí)現(xiàn)響應(yīng)式,因?yàn)橄旅鏁?huì)把 state 設(shè)置到創(chuàng)建的 Vue 上來實(shí)現(xiàn)響應(yīng)式酸钦,不過源碼中就是用的set
Vue.set(parent, path[path.length - 1], module.state)
// parent[path[path.length - 1]] = module.state // 但是這樣操作子模塊不是響應(yīng)式的
}
module.forEachMutation((mutation, key) => {
store.mutations[key] = store.mutations[key] || []
store.mutations[key].push(payload => mutation.call(store, module.state, payload))
})
module.forEachAction((action, key) => {
store.actions[key] = store.actions[key] || []
store.actions[key].push(payload => action.call(store, store, payload))
})
module.forEachChildren((childModule, key) => {
installMudole(store, path.concat(key), childModule, rootState) // childModule.state
})
// 沒用namespace邀泉,則所有模塊的getters默認(rèn)都會(huì)合并到一個(gè)對(duì)象里,都是直接getters.xxx即可钝鸽,而不用getters.a.xxx
module.forEachGetters((getterFn, key) => {
store.wrapGetters[key] = () => getterFn.call(store, module.state)
})
}
export class Store {
constructor (options) {
// 格式化用戶傳入的配置,格式化成樹結(jié)構(gòu)
this._modules = new ModuleCollection(options)
this.mutations = {} // 將用戶所有模塊的mutation都放到這個(gè)對(duì)象中
this.actions = {} // 將用戶所有模塊的action都放到這個(gè)對(duì)象中
this.getters = {}
this.wrapGetters = {} // 臨時(shí)變量庞钢,存儲(chǔ)getters
const state = options.state // 用戶傳入的全局state拔恰,還是非響應(yīng)式的
// 將所有模塊中的mutations和actions合并到全局上,合并state和getters到全局上
installMudole(this, [], this._modules.root, state)
// 初始化與重置(源碼中因?yàn)樾枰獙?duì)熱更新進(jìn)行判斷基括,熱更新需要重置颜懊,但這里就是單純的初始化)
// 主要干兩件事:將state設(shè)置成響應(yīng)式掛到store._vm上(通過new Vue),將getters掛到computed上
resetStoreVM(this, state)
}
get state () { // 屬性訪問器
return this._vm._data.$$state
}
}
function resetStoreVM (store, state) {
const computed = {}
forEachValue(store.wrapGetters, (fn, key) => {
computed[key] = fn // 將是所有的屬性放到computed中
Object.defineProperty(store.getters, key, {
get: () => store._vm[key]
})
})
// 用戶肯定是先使用 Vue.use,再進(jìn)行 new Vue.Store({...})河爹,所以這里的 Vue 已經(jīng)是可以拿到構(gòu)造函數(shù)的了
// 必須放到f forEachValue 后面匠璧,確保 computed 已經(jīng)有值
store._vm = new Vue({
data: {
// Vue中不會(huì)對(duì) $開頭的屬性進(jìn)行代理操作(不會(huì)掛到_vm上進(jìn)行代理)
// 但是其屬性依舊會(huì)被代理到(頁(yè)面獲取時(shí)依然會(huì)被收集依賴),因?yàn)槲覀儾粫?huì)直接操作state咸这,而是操作state.xxx夷恍,性能優(yōu)化
$$state: state
},
computed
})
}
實(shí)現(xiàn)commit和dispatch
記錄了namespace后媳维,在獲取與調(diào)用對(duì)應(yīng)方法時(shí)酿雪,則是通過路徑名+方法的方式來調(diào)用。比如commit('a/getterAge', 20)
侄刽,dispatch
也是如此指黎。因此在初始化installMudole
時(shí),需要將mutations
/actions
/getters
都加上對(duì)應(yīng)路徑州丹。當(dāng)然這里的實(shí)現(xiàn)是不健全的醋安,vuex中如果存在namespace,則dispatch里使用commit墓毒,是不需要帶上相對(duì)路徑的吓揪,會(huì)去找自己的mutations
中對(duì)應(yīng)的方法,這里并未實(shí)現(xiàn)
// store.js
const installMudole = (store, path, module, rootState) => {
+ const namespace = store._modules.getNamespace(path)
// code...
module.forEachMutation((mutation, key) => {
+ store.mutations[namespace + key] = store.mutations[namespace + key] || []
+ store.mutations[namespace + key].push(payload => mutation.call(store, module.state, payload))
})
module.forEachAction((action, key) => {
+ store.actions[namespace + key] = store.actions[namespace + key] || []
+ store.actions[namespace + key].push(payload => action.call(store, store, payload))
})
// 沒用namespace蚁鳖,則所有模塊的getters默認(rèn)都會(huì)合并到一個(gè)對(duì)象里磺芭,都是直接getters.xxx即可,而不用getters[a/xxx]
module.forEachGetters((getterFn, key) => {
+ store.wrapGetters[namespace + key] = () => getterFn.call(store, module.state)
})
}
export class Store {
+ commit = (type, payload) => { // ES7語(yǔ)法醉箕,類的箭頭函數(shù)钾腺,表示this永遠(yuǎn)指向store實(shí)例
+ if (this.mutations[type]) {
+ this.mutations[type].forEach(fn => fn(payload)) // 不同于之前,現(xiàn)在的mutations已經(jīng)是個(gè)包含模塊中mutations的數(shù)組
+ }
+ }
+ dispatch = (type, payload) => {
+ if (this.actions[type]) {
+ this.actions[type].forEach(fn => fn(payload))
+ }
+ }
}
module中添加獲取命名空間讥裤,構(gòu)建成樹結(jié)果時(shí)可進(jìn)行命名空間判斷放棒,是否需要添加成a/method
的形式,否則調(diào)用路徑依舊為全局直接調(diào)方法名的方式
// module/module.js
export default class Module {
+ get namespaced () {
+ return !!this._raw.namespaced
+ }
}
// module/module-collection.js
export default class ModuleCollection {
+ getNamespace (path) {
+ let module = this.root
+ return path.reduce((namespaced, key) => {
+ module = module.getChild(key)
+ // 如果父模塊沒有namespaced己英,子模塊有间螟,那么調(diào)用的時(shí)候就只需要寫子模塊,比如 c/ 否則就是a/c/
+ return namespaced + (module.namespaced ? key + '/' : '')
+ }, '')
+ }
}
插件實(shí)現(xiàn)
這里不是在實(shí)現(xiàn)vuex
损肛,而是在實(shí)現(xiàn)自己開發(fā)一個(gè)vuex
插件厢破,因?yàn)椴粫?huì)實(shí)現(xiàn)plugin,所以需要自行切換成原生vuex
或許大多數(shù)人都不知道vuex
插件治拿,官網(wǎng)高階中有寫摩泪,傳送門:vuex插件
面試題:如何實(shí)現(xiàn)vuex持久化緩存?
-
vuex
無(wú)法實(shí)現(xiàn)持久化緩存劫谅,頁(yè)面刷新的時(shí)候就會(huì)清除已經(jīng)保存的數(shù)據(jù)见坑。而有一個(gè)插件就是專門用于解決vuex
持久化問題的:vuex-persist
那為什么有localstorage
嚷掠,還需要借助vuex-persist
呢? - 因?yàn)?code>localstorage數(shù)據(jù)變了頁(yè)面數(shù)據(jù)也不會(huì)自動(dòng)刷新荞驴,并非響應(yīng)式的
插件接收一個(gè)數(shù)組不皆,數(shù)組每一項(xiàng)均為函數(shù),如果有多個(gè)插件熊楼,自上而下執(zhí)行
官方提供了一個(gè)開發(fā)使用的插件logger
霹娄,當(dāng)然因?yàn)榛径紩?huì)安裝vue-devtools
,所以并不會(huì)用到
vuex主要提供了兩個(gè)方法來讓用戶自定義插件孙蒙,分別是subscribe
和replaceState
项棠,subscribe
用于訂閱觸發(fā)commit
事件,replaceState
用于初始化時(shí)替換頁(yè)面數(shù)據(jù)
支持自定義模式挎峦,這里replaceState
只實(shí)現(xiàn)storage
插件的實(shí)現(xiàn)思路比較簡(jiǎn)單香追,就是發(fā)布訂閱。但是有一個(gè)問題坦胶,就是installMudole
中透典,之前的實(shí)現(xiàn)是通過用戶定義的state
(掛載store._vm._data.$$state上),初始化模塊時(shí)顿苇,會(huì)為commit注冊(cè)事件
而replaceState
的實(shí)現(xiàn)峭咒,更改的是store
上的state
,導(dǎo)致視圖渲染無(wú)效纪岁。因此需要在commit
時(shí)重新去store
上獲取對(duì)應(yīng)的值
+ // 最開始定義的時(shí)候凑队,用的是用戶傳入的 state,但是一旦執(zhí)行了replaceState幔翰,則 $$state 被替換
+ // Vue.set(parent, path[path.length - 1], module.state) 用的是最初傳入定義成響應(yīng)式的state(也就是rootState),而replaceState設(shè)置的是store的state
+ // 一個(gè)是 module的state漩氨,一個(gè)是變更的store的state,就會(huì)導(dǎo)致commit時(shí)數(shù)據(jù)取值不正確(一直是舊數(shù)據(jù))遗增,所以需要去store上重新獲取
+ const getState = (store, path) => { // store.state獲取的是最新狀態(tài)
+ return path.reduce((rootState, current) => {
+ return rootState[current]
+ }, store.state)
+ }
const installMudole = (store, path, module, rootState) => {
// code...
module.forEachMutation((mutation, key) => {
store.mutations[namespace + key] = store.mutations[namespace + key] || []
- store.mutations[namespace + key].push(payload => mutation.call(store, module.state, payload))
+ store.mutations[namespace + key].push(payload => mutation.call(store, getState(store, path), payload))
})
// 沒用namespace叫惊,則所有模塊的getters默認(rèn)都會(huì)合并到一個(gè)對(duì)象里,都是直接getters.xxx即可做修,而不用getters[a/xxx]
module.forEachGetters((getterFn, key) => {
- store.wrapGetters[namespace + key] = () => getterFn.call(store, module.state)
+ store.wrapGetters[namespace + key] = () => getterFn.call(store, getState(store, path))
})
}
export class Store {
constructor (options) {
// code...
+ this._subscribe = [] // 因?yàn)槟軅魅攵鄠€(gè)插件霍狰,所以會(huì)有多個(gè)訂閱
+ // 默認(rèn)插件就會(huì)被執(zhí)行,從上往下執(zhí)行
+ options.plugins.forEach(plugin => plugin(this))
}
+ subscribe (fn) {
+ this._subscribe.push(fn)
+ }
+ replaceState (newState) {
+ this._vm._data.$$state = newState
+ }
commit = (type, payload) => { // ES7語(yǔ)法饰及,類的箭頭函數(shù)蔗坯,表示this永遠(yuǎn)指向store實(shí)例
if (this.mutations[type]) {
this.mutations[type].forEach(fn => fn(payload)) // 不同于之前,現(xiàn)在的mutations已經(jīng)是個(gè)包含模塊中mutations的數(shù)組
// 變更后燎含,觸發(fā)插件訂閱執(zhí)行
+ this._subscribe.forEach(fn => fn({ type, payload }, this.state))
}
}
}
斷言 - 非法操作實(shí)現(xiàn)
原生vuex
步悠,如果采用嚴(yán)格模式strict: true
,那么在mutations
中采用異步待會(huì)將會(huì)報(bào)錯(cuò)瘫镇,非合法操作也會(huì)報(bào)錯(cuò)
主要通過在store
中_withCommiting
來包裹合法操作賦值鼎兽,實(shí)現(xiàn)思路是通過watcher
進(jìn)行監(jiān)聽(同步,深度)铣除,store
中添加標(biāo)記位谚咬,當(dāng)數(shù)據(jù)變化時(shí),如果斷言為false則會(huì)出現(xiàn)報(bào)錯(cuò)尚粘。挺好懂的择卦,文章已經(jīng)這么長(zhǎng)了,能看到這里你估計(jì)只是來復(fù)習(xí)底層的郎嫁,直接看代碼
const installMudole = (store, path, module, rootState) => {
if (path.length > 0) { // 是子模塊
- Vue.set(parent, path[path.length - 1], module.state)
+ store._withCommiting(() => Vue.set(parent, path[path.length - 1], module.state))
}
}
export class Store {
constructor (options) {
// code...
+ this.strict = options.strict
+ this._commiting = false
+ this._withCommiting = function (fn) {
+ const commiting = this._commiting
+ this._commiting = true
+ fn() // 修改狀態(tài)的邏輯
+ this._commiting = !commiting
+ }
}
replaceState (newState) {
- this._vm._data.$$state = newState
+ this._withCommiting(() => (this._vm._data.$$state = newState))
}
commit = (type, payload) => { // ES7語(yǔ)法秉继,類的箭頭函數(shù),表示this永遠(yuǎn)指向store實(shí)例
if (this.mutations[type]) {
- this.mutations[type].forEach(fn => fn(payload))
+ // 執(zhí)行_withCommiting時(shí)泽铛,_commiting為true尚辑,所以不會(huì)報(bào)錯(cuò)
+ // 如果mutations中有異步代碼,那么異步代碼執(zhí)行后盔腔,觸發(fā)watcher監(jiān)聽變化杠茬,此時(shí)的_commiting會(huì)為false,就會(huì)報(bào)錯(cuò)
+ this._withCommiting(() => this.mutations[type].forEach(fn => fn(payload))) // 不同于之前弛随,現(xiàn)在的mutations已經(jīng)是個(gè)包含模塊中mutations的數(shù)組
// 變更后瓢喉,觸發(fā)插件訂閱執(zhí)行
this._subscribe.forEach(fn => fn({ type, payload }, this.state))
}
}
}
function resetStoreVM (store, state) {
// code...
+ if (store.strict) {
+ // 因?yàn)閣atcher執(zhí)行時(shí)異步的,需要加上 {sync: true} 設(shè)置為同步舀透,文檔沒有栓票,需要自行看源碼
+ store._vm.$watch(() => store._vm._data.$$state, () => {
+ console.assert(store._commiting, '非法操作')
+ }, { sync: true, deep: true })
+ }
}