一、Vuex介绍
Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state) 。Vuex 和单纯的全局对象有以下两点不同:
Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新。- 不能直接改变
store中的状态。改变store中的状态的唯一途径就是显式地 提交commit(mutation)。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
二、Vuex使用
1. Vuex在Vue项目中的接入步骤
- 第一步:引入
Vuex插件库,使用Vue.use(Vuex)进行挂在; - 第二步:使用
new Vuex.Store(options)创建一个Store实例对象,用来作为VueApp的全局状态管理器; - 第三步:在
new Vue(options)创建一个VueApp的根组件时,将第二步中的Store实例以store的属性传入到这个options配置中,即可在这个VueAPP应用的所有组件中通过this.$store属性拿到这个Store实例对象的全局状态管理器。
2. Vuex的配置说明和使用方式
Vuex的Store实例化参数options:
- status:单一状态树,用一个对象就包含了全部的应用层级状态
- getter:计算属性,函数对象类型数据,从
state中派生出一些状态, 监听status的状态自动执行对应函数,返回最新属性值 - mutations:同步方式更新
status属性值的方法,也是唯一改变status的合法方式,不允许直接修改status的数据 - actions:带有异步操作的方式更新
status属性值的方法,一部操作执行后内部在调用mutations的方法修改status的数据状态 - modules: 用来按照指定的模块数据结构来构建全局的状态管理器,在大型项目中,当全局数据状态比较多时,可以更方便清晰的管理所有状态,内部对应的key为每个模块名称,值为对应子模块的配置参数。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const persits = (store) => {
store.subscribe((mutations, state) => {
console.log('persits plugins had runed!', mutations, state)
})
}
export default new Vuex.Store({
plugins: [persits],
state: {
firstName: '张',
lastName: '三丰',
age: 98
},
getters: {
fullName (state) {
return state.firstName + state.lastName
}
},
mutations: {
changeName (state, payload) {
state.lastName = payload
},
syncAddAge (state, payload) {
console.log('root mutations: syncAddAge runed!')
state.age += payload
},
muintAge (state, payload) {
state.age -= payload
}
},
actions: {
asyncMuintAge ({ commit }, payload) {
console.log('root actions: asyncMuintAge runed!')
setTimeout(() => {
commit('muintAge', payload)
}, 1000)
}
},
// 所有的getters都是直接挂载在全局的store.getters上面的,
// 如果父模块已经有这个名称的getters,Vuex会报错提示,读取的是用父模块的getters,
// 如果多个模块有相同名称的getters,Vuex会报错提示,从第一个模块开始深度遍历读取
modules: {
moduleA: {
state: {
jobType: '开发',
desc: '写代码的码农~~~',
age: 10
},
getters: {
// 模块内部的 getter 函数参数:
// 第一个参数为该模块的局部状态state,
// 第二个参数为store的全部getters对象,即所有模块的getters的集合,
// 第三个参数为根节点的状态对象
develop (state, getters, rootState) {
console.log('模块内部getters的第二个参数:', getters)
console.log(rootState.firstName + rootState.lastName)
return '第一个一级模块的getter数据:' + state.jobType + ': ' + state.desc
},
webA (state) {
return '第一个一级模块的getter数据:' + state.jobType + ' ' + state.desc
}
},
mutations: {
// 模块内部的 mutations 函数参数:
// 第一个参数为该模块的局部状态state
// 第二个参数为调用时传入的载荷payload
syncAddAge (state, payload) {
console.log('moduleA mutations: syncAddAge runed!')
state.age += payload
},
renameJob (state, payload) {
// 这里的 `state` 对象是模块的局部状态
state.jobType = payload
}
},
actions: {
// 模块内部的 mutations 函数参数:
// 第一个参数为该模块的store对象实例,包含了 该模块的局部状态state、Store的commit方法、根节点的状态对象rootState
// 第二个参数为调用时传入的载荷payload
aynscRenameJob ({ state, commit, rootState }, payload) {
// 模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState:
if ((Object.keys(state).length + rootState.age) % 2 === 1) {
commit('renameJob', payload)
}
}
},
modules: {
subModule1: {
state: {
jobName: '前端',
salary: '18K'
},
getters: {
web (state) {
return '二级模块的getter数据:' + state.jobName + ' ' + state.salary
}
},
actions: {
asyncMuintAge ({ commit }, payload) {
console.log('moduleA.subModule1 actions: asyncMuintAge runed!', payload)
}
}
}
}
},
moduleB: {
state: {
jobName: '产品',
desc: '设计思考产品功能的~~~'
},
getters: {
production (state) {
return state.jobName + ' ' + state.desc
},
developB (state) {
return '第二个一级模块的getter数据:' + state.jobName + ' ' + state.desc
},
webB (state) {
return '第二个一级模块的getter数据:' + state.jobName + ' ' + state.desc
}
}
}
}
})
从上面的使用看,Vuex的模块化使用有一下特点:
- 所有的
getter都是直接挂载在全局的store.getters上面的,如果父模块已经有这个名称的getter,Vuex会报错提示,读取的是用父模块的getter,如果多个模块有相同名称的getter,Vuex会报错提示,显示的数据是从所有模块树中深度遍历读取(从上往下,一个一个的深度查找).可以通过给模块添加namespaced: true为命名空间来避免这个读取问题 store中的所有getter,第一个参数为模块局部的状态对象,第二个参数为store的所有getters对象,根节点状态会作为第三个参数暴露出来;store中的所有mutation,接收的第一个参数是模块的局部状态对象;- 对于模块内部的
action,局部状态通过context.state暴露出来,根节点状态则为context.rootState; - 多个模块之间重名的
mutation,执行this.$store.commit('mutationName', payload),所有模块中名为mutationName的mutation都会被执行。可以通过给模块添加namespaced: true为命名空间来避免这个执行问题 - 多个模块之间重名的
actions,执行this.$store.dispatch('actionsName', payload),所有模块中名为actionsName的actions都会被执行。可以通过给模块添加namespaced: true为命名空间来避免这个执行问题 vuex创建实例的时候,可以给该store传入插件,可以监听在提交commit修改state的时候触发插件执行。每一个插件就是一个函数,接受参数为当前的store,该store有一个subscribe的订阅事件的方法,接受参数为store的mutations和state.
3. Vuex模块化命名空间
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
如果希望模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:
const store = new Vuex.Store({
modules: {
account: {
// 一级子模块启用命名空间,访问 getters mutations actions就需要带上对应的路径
namespaced: true,
state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
mutations: {
login () { ... } // -> commit('account/login')
},
actions: {
login () { ... } // -> dispatch('account/login')
},
// 嵌套模块
modules: {
// 继承父模块的命名空间
myPage: {
state: () => ({ ... }),
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// 继承父模块的命名空间的同时,进一步嵌套命名空间
posts: {
namespaced: true,
state: () => ({ ... }),
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
启用了命名空间的 getter 和 action 会收到局部化的 getter,dispatch 和 commit。换言之,你在使用模块内容时不需要在同一模块内额外添加空间名前缀。更改 namespaced 属性后不需要修改模块内的代码。
a. 在带命名空间的模块内访问全局内容
如果你希望使用全局 state 和 getter,rootState 和 rootGetters 会作为第三和第四参数传入 getter,也会通过 context 对象的属性传入 action。
若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。
modules: {
foo: {
namespaced: true,
getters: {
// 在这个模块的 getter 中,`getters` 被局部化了
// 你可以使用 getter 的第四个参数来调用 `rootGetters`
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 'foo/someOtherGetter'
rootGetters.someOtherGetter // -> 'someOtherGetter'
},
someOtherGetter: state => { ... }
},
actions: {
// 在这个模块中, dispatch 和 commit 也被局部化了
// 他们可以接受 `root` 属性以访问根 dispatch 或 commit
someAction ({ dispatch, commit, getters, rootGetters }, payload) {
getters.someGetter // -> 'foo/someGetter'
rootGetters.someGetter // -> 'someGetter'
dispatch('someOtherAction', payload) // -> 'foo/someOtherAction'
dispatch('someOtherAction', payload, { root: true }) // -> 'someOtherAction'
commit('someMutation', payload) // -> 'foo/someMutation'
commit('someMutation', payload, { root: true }) // -> 'someMutation'
},
someOtherAction (ctx, payload) { ... }
}
}
}
b. 在带命名空间的模块注册全局action
若需要在带命名空间的模块注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中。例如:
{
actions: {
someOtherAction ({dispatch}) {
dispatch('someAction')
}
},
modules: {
foo: {
namespaced: true,
actions: {
someAction: {
root: true,
handler (namespacedContext, payload) { ... } // -> 'someAction'
}
}
}
}
}
c. 带命名空间的绑定函数
当使用 mapState, mapGetters, mapActions 和 mapMutations 这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:
computed: {
...mapState({
a: state => state.some.nested.module.a,
b: state => state.some.nested.module.b
})
},
methods: {
...mapActions([ 'some/nested/module/foo', // -> this['some/nested/module/foo']()
'some/nested/module/bar' // -> this['some/nested/module/bar']()
])
}
对于这种情况,你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。于是上面的例子可以简化为:
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
])
}
而且,你可以通过使用 createNamespacedHelpers 创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions([
'foo',
'bar'
])
}
}
d. 模块动态注册
在 store 创建之后,你可以使用 store.registerModule 方法注册模块:
import Vuex from 'vuex'
const store = new Vuex.Store({ /* 选项 */ })
// 注册模块 `myModule`
store.registerModule('myModule', {
// ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
// ...
})
之后就可以通过 store.state.myModule 和 store.state.nested.myModule 访问模块的状态。
你也可以使用 store.unregisterModule(moduleName) 来动态卸载模块。注意,不能使用此方法卸载静态模块(即创建 store 时声明的模块),可以通过 store.hasModule(moduleName) 方法检查该模块是否已经被注册到 store。
e. 保留state
在注册一个新 module 时,你很有可能想保留过去的 state,例如从一个服务端渲染的应用保留 state。你可以通过 preserveState 选项将其归档:store.registerModule('a', module, { preserveState: true })。
当你设置 preserveState: true 时,该模块会被注册,action、mutation 和 getter 会被添加到 store 中,但是 state 不会。这里假设 store 的 state 已经包含了这个 module 的 state 并且你不希望将其覆写。
f. 模块重用
有时我们可能需要创建一个模块的多个实例,例如:
- 创建多个 store,他们公用同一个模块 (例如当
runInNewContext选项是false或'once'时,为了在服务端渲染中避免有状态的单例) - 在一个 store 中多次注册同一个模块
如果我们使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时 store 或模块间数据互相污染的问题。
实际上这和 Vue 组件内的 data 是同样的问题。因此解决办法也是相同的——使用一个函数来声明模块状态(仅 2.3.0+ 支持):
const MyReusableModule = {
state: () => ({
foo: 'bar'
}),
// mutation, action 和 getter 等等...
}
三、Vuex重点解析
1. Vuex数据的混入
Vue在使用Vuex之前,要执行Vue.use(Vuex)来载入Vuex,然后再使用new Vuex.Store(options)创建Vuex的数据实例对象,并在创建Vue的根组件的时候,以store参数配置到Vue根组件中。
在执行Vue.use(Vuex)的时候,会自动调用Vuex的install方法,,传入的参数是Vue的构造函数,在这个方法中使用Vue的mixin方法,给Vue组件的beforeCreate钩子函数全局混入给所有Vue实例挂载相同的store实例对象。主要逻辑代码如下:
function install (_Vue) {
// Vue.use(Vuex)的时候会执行Vuex的install方法
Vue = _Vue // 在Vuex中全局保存Vue构造函数,在其他的地方会有使用到
Vue.mixin({
// Vuex混入一个createBefore函数,每次new Vue实例的时候都会执行这个钩子函数,
// 然后在执行组件自定义的createBefore函数
beforeCreate () {
console.log('混入函数到所有vue实例中!,在这里将获取每个组件实例,并将$store注册到实例上去')
console.log(this.$options) // 每个vue组件实例创建时的配置对象this.$options
if (this.$options && this.$options.store) {
// 组件的配置对象上存在store,表示这个是根组件实例对象
this.$store = this.$options.store
} else {
// 其他子组建,自身的$options上是没有store对象的,可以从父组件上取之前注册上去的$store
this.$store = this.$parent && this.$parent.$store
}
}
})
}
这里的难点在于怎样把全局唯一的Store实例对象注册到每一个Vue组件实例的$store属性上,从上面代码的逻辑可以看出,在Vue初始化创建APP应用的根组件时,会将全局的Store实例对象传入,所以在根组件的$options上就可以拿到这个Store实例对象,同时将Store实例对象设置到根组件的$store上。其他子组件想要取到这个Store实例对象,就可以从它的父组件对象上面取到这个Store实例对象,并设置到每个子组件实例的$store上,这样就可以在应用的任何组件实例上通过$store参数取到这个Store实例。
2. Vuex状态数据的响应式
Vuex的status数据是响应式的,直接将options.store.state赋值给Store实例的state肯定不会是响应式的,这样的话直接修改store的state数据时,页面是不是响应式的更新的。为了结果这个问题,可以采用以下的方式:
class Store {
constructor (options) {
// 在注册Store创建实例对象的时候执行的函数,传入的就是store的配置对象
// 将配置对象添加到Vuex的实例上之后,就可以在组件中通过this.$store对象访问
// this.state = options.state // 这样直接将配置对象options.state赋值给this.state,数据不是响应式的
// 需要借助Vue的响应式原理,通过 new Vue() 方式将this.store变成响应式的
this.s = new Vue({
data () {
return { state: options.state }
}
})
}
// 为了能够通过 this.state 直接读取到中间变量 this.s 上的响应式数据,需要在这里使用类的get查询器做一个代理
get state () {
return this.s.state
}
}
响应式数据可以直接依赖于Vue的响应式系统,如上代码所示,将配置的options.state状态数据对象直接设置给一个Vue实例的data属性即可,然后要能直接从Store实例的state上读取到这个,可以给这个Store实例添加一个查询器参数,读取这个参数时,返回这个Vue实例的响应式数据。
3. 实现简易的Vuex功能类
根据Vuex的使用方式,实现如下的一个简易版本的Vuex,能够获取响应式的state状态数据、配置getters的计算属性、commit同步提交数据变更、dispatch异步提交数据变更的基本功能操作。
class Store {
constructor (options) {
// 在注册Store创建实例对象的时候执行的函数,传入的就是store的配置对象
// 将配置对象添加到Vuex的实例上之后,就可以在组件中通过this.$store对象访问
// this.state = options.state // 这样直接将配置对象options.state赋值给this.state,数据不是响应式的
// 需要借助Vue的响应式原理,通过 new Vue() 方式将this.store变成响应式的
this.state = new Vue({
data () {
return options.state
}
})
// 给Store实例对象$store添加getters对象属性
this.getters = {}
Object.entries(options.getters).forEach(([getterName, fn]) => {
// this.getters[getterName] = fn(this.state) // 这里的getters是计算属性,每次读取都需要根据state的数据动态计算。直接赋值是不行的
Object.defineProperty(this.getters, getterName, {
get: () => {
return fn(this.state)
}
})
})
// 给Store实例对象$store添加mutations对象属性
this.mutations = {}
Object.entries(options.mutations).forEach(([mutationName, fn]) => {
this.mutations[mutationName] = (payload) => {
fn(this.state, payload)
}
})
// 给Store实例对象$store添加actions对象属性
this.actions = {}
Object.entries(options.actions).forEach(([actionName, fn]) => {
this.actions[actionName] = (payload) => {
fn(this, payload)
}
})
}
// 给Store实例对象$store添加commit方法
commit = (mutationName, payload) => {
this.mutations[mutationName](payload)
}
// 给Store实例对象$store添加dispatch方法
dispatch = (actionName, payload) => {
this.actions[actionName](payload)
}
}
4. Vuex的模块化实现
Vuex首先将用户传入的options对象重新组装成下面的格式:
let root = { // 根的数据节点,然后递归生产下面的树状结构
_row: options, // 当前节点的options
_children: { // 当前节点的子模块
moduleA: {
_row: {},
_children: {}
tate: options.state
},
moduleB: {}
},
state: options.state // 当前节点的state数据
}
vuex的模块化的实现逻辑代码: 具体实现如下:
- 采用
ModuleCollection类递归将用户传入的options构造出树形的结构 - 调用
installModule函数将模块挂在到store下面
let Vue
class ModuleCollection {
constructor (options) {
this.register([], options) // 递归将模块注册为对应的树结构
}
register (path, rootModule) {
const module = { // 将模块格式化
_rowModule: rootModule,
_children: {},
state: rootModule.state
}
if (path.length === 0) {
// 根模块
this.root = module
} else {
// 递归后续进来
// 直接这样写是有问题的, 多级的字模块会被格式化为一级了, moduleA的子模块变成了第三个模块
// this.root._children[path[path.length - 1]] = module
const parent = path.slice(0, -1).reduce((root, current) => {
return root._children[current]
}, this.root)
parent._children[path[path.length - 1]] = module
}
// 当前模块是否有modules
if (rootModule.modules) {
Object.entries(rootModule.modules).forEach(([moduleName, module]) => {
this.register(path.concat(moduleName), module)
})
}
}
}
function installModule (store, rootState, path, rootModule) {
if (path.length) {
// 是子模块,需要找到父模块,并将自己的状态发放上去,实现查找挂载数据格式
// rootModule = rootModule._children[path[path.length-1]]
const parent = path.slice(0, -1).reduce((root, current) => {
return root[current]
}, rootState)
// 这样设置属性不是响应式的
// parent[path[path.length - 1]] = rootModule.state
Vue.set(parent, path[path.length - 1], rootModule.state)
}
// 以下代码都是在处理 模块中的getters、mutations、actions
// 挂在所有的getters
const getters = rootModule._rowModule.getters
if (getters) {
Object.entries(getters).forEach(([getterName, fn]) => {
// 将所有模块的getters都挂载在store实例的getters属性上面
Object.defineProperty(store.getters, getterName, {
get () {
// 将当前模块的state -—— rootModule.state作为第一个参数传入
return fn(rootModule.state, store.getters, rootState)
}
})
})
}
// 挂在所有的mutations,同名的mutations会依次批量执行,所有需要用数组存贮
const mutations = rootModule._rowModule.mutations
if (mutations) {
Object.entries(mutations).forEach(([mutationName, fn]) => {
// 模块重名的时候会依次执行没有个重名的mutations,所有下面直接等于一个函数是不行的,
// 而是要以数组的形式存起来 store.mutations = {mutationName: [mutationFn1, mutationFn2]}
// store[mutationName] = function () {}
const mutationsArr = store.mutations[mutationName] || []
mutationsArr.push((payload) => {
fn(rootModule.state, payload)
store._subscribe.forEach(fn => fn({ type: mutationName, payload }, rootState))
})
store.mutations[mutationName] = mutationsArr
})
}
// 挂在所有的mutations,同名的mutations会依次批量执行,所有需要用数组存贮
const actions = rootModule._rowModule.actions
if (actions) {
Object.entries(actions).forEach(([actionName, fn]) => {
// 模块重名的时候会依次执行没有个重名的actions,所有下面直接等于一个函数是不行的,
// 而是要以数组的形式存起来 store.actions = {actionName: [actionFn1, actionFn2]}
// store[actionName] = function () {}
const actionsArr = store.actions[actionName] || []
actionsArr.push((payload) => {
fn(store, payload)
})
store.actions[actionName] = actionsArr
})
}
// 递归挂在子模块
Object.entries(rootModule._children).forEach(([moduleName, module]) => {
installModule(store, rootState, path.concat(moduleName), module)
})
}
class Store {
constructor (options) {
// 在注册Store创建实例对象的时候执行的函数,传入的就是store的配置对象
// 将配置对象添加到Vuex的实例上之后,就可以在组件中通过this.$store对象访问
// this.state = options.state // 这样直接将配置对象options.state赋值给this.state,数据不是响应式的
// 需要借助Vue的响应式原理,通过 new Vue() 方式将this.store变成响应式的
this.s = new Vue({
data () {
return { state: options.state }
}
})
// 给Store实例对象$store添加getters对象属性
this.getters = {} //
// 给Store实例对象$store添加mutations对象属性
this.mutations = {}
// 给Store实例对象$store添加actions对象属性
this.actions = {}
// plugins
this._subscribe = []
this._modules = new ModuleCollection(options) // 将原数据数据格式化为一个想要的数据结构
console.log('组装之后的state树结果:', this._modules)
// 递归将结果进行分类处理:
// this 整个store
// this.state 当前的根状态
// []为了递归创建的
// this._modules.root 从根模块开始安装
installModule(this, this.state, [], this._modules.root)
// 执行plugins
if (options.plugins) {
options.plugins.forEach(plugin => plugin(this))
}
}
subscribe = (fn) => {
this._subscribe.push(fn)
}
// 给Store实例对象$store添加commit方法
commit = (mutationName, payload) => {
// this.mutations[mutationName](payload)
this.mutations[mutationName].forEach(fn => fn(payload))
}
// 给Store实例对象$store添加dispatch方法
dispatch = (actionName, payload) => {
// this.actions[actionName](payload)
this.actions[actionName].forEach(fn => fn(payload))
}
get state () {
return this.s.state
}
}
function install (_Vue) {
// Vue.use(Vuex)的时候会执行Vuex的install方法
console.log('install')
Vue = _Vue
Vue.mixin({
// Vuex混入一个createBefore函数,每次new Vue实例的时候都会执行这个钩子函数,让后在执行组件自定义的createBefore函数
beforeCreate () {
// console.log('混入函数到所有vue实例中!,在这里将获取每个组件实例,并将$store注册到实例上去')
// console.log(this) // 每个vue组件实例创建时候到配置对象 this.$options
if (this.$options && this.$options.store) {
// 组件的配置对象上存在store,表示这个是根组件实例对象
this.$store = this.$options.store
} else {
// 其他子组建,自身的$options上是没有store对象的,可以从父组件上取之前注册上去的$store
this.$store = this.$parent && this.$parent.$store
}
}
})
}
export default {
Store,
install
}
四、Vuex原生实现
// my-vuex.js
let Vue
class Store {
constructor (options) {
this.state = new Vue({
data () {
return options.state
}
})
// 给Store实例对象$store添加getters对象属性
this.getters = {}
Object.entries(options.getters).forEach(([getterName, fn]) => {
// this.getters[getterName] = fn(this.state)
// 这里的getters是计算属性,每次读取都需要根据state的数据动态计算。直接赋值是不行的
Object.defineProperty(this.getters, getterName, {
get: () => {
return fn(this.state)
}
})
})
// 给Store实例对象$store添加mutations对象属性
this.mutations = {}
Object.entries(options.mutations).forEach(([mutationName, fn]) => {
this.mutations[mutationName] = (payload) => {
fn(this.state, payload)
}
})
// 给Store实例对象$store添加actions对象属性
this.actions = {}
Object.entries(options.actions).forEach(([actionName, fn]) => {
this.actions[actionName] = (payload) => {
fn(this, payload)
}
})
}
// 给Store实例对象$store添加commit方法
commit = (mutationName, payload) => {
this.mutations[mutationName](payload)
}
// 给Store实例对象$store添加dispatch方法
dispatch = (actionName, payload) => {
this.actions[actionName](payload)
}
// 模块配置参数格式化
}
function install (_Vue) {
// Vue.use(Vuex)的时候会执行Vuex的install方法
console.log('install')
Vue = _Vue
Vue.mixin({
// Vuex混入一个createBefore函数,每次new Vue实例的时候都会执行这个钩子函数,
// 让后在执行组件自定义的createBefore函数
beforeCreate () {
console.log('混入函数到所有vue实例中!,在这里将获取每个组件实例,并将$store注册到实例上去')
console.log(this) // 每个vue组件实例创建时候到配置对象 this.$options
if (this.$options && this.$options.store) {
// 组件的配置对象上存在store,表示这个是根组件实例对象
this.$store = this.$options.store
} else {
// 其他子组建,自身的$options上是没有store对象的,可以从父组件上取之前注册上去的$store
this.$store = this.$parent && this.$parent.$store
}
}
})
}
export default {
Store,
install
}