Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
https://vuex.vuejs.org/zh/
vuex 是 Vue 配套的公共数据管理库,它可以把一些共享的数据,保存到 vuex 中,方便整个程序中的任何组件直接获取或修改我们的公共数据。使用Vuex的时候需要用到Vue的use方法,因此它的本质是一个插件。
store/index.js(Vuex入口文件)
import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({// 用于保存全局共享数据state: {name: 'lnj'},getters: { }// 用于同步修改共享数据mutations: { },// 用于异步修改共享数据actions: { },// 用户模块化共享数据modules: { }
})
main.js(Vue中引入Vuex)
import Vue from 'vue'
import App from './App.vue'
import store from './store' Vue.config.productionTip = falsenew Vue({
store,
render: h => h(App)
}).$mount('#app')
通过分析它的基本结构,实现一个简易版的Vuex。
把官方的vuex替换成自己的myVuex
import Vue from 'vue'
import Vuex from './myVuex'Vue.use(Vuex)
...
在使用Vuex的时候会通过Vuex.Store创建一个仓库,而且为了保证每个Vue实例中都能通过this.$store拿到仓库,还需要给每个Vue实例添加一个$store属性。
官方初始化Store的部分代码,如下所示:
function applyMixin (Vue) {var version = Number(Vue.version.split('.')[0]);if (version >= 2) {Vue.mixin({ beforeCreate: vuexInit });} else {var _init = Vue.prototype._init;Vue.prototype._init = function (options) {if ( options === void 0 ) options = {};options.init = options.init? [vuexInit].concat(options.init): vuexInit;_init.call(this, options);};}function vuexInit () {var options = this.$options;if (options.store) {this.$store = typeof options.store === 'function'? options.store(): options.store;} else if (options.parent && options.parent.$store) {this.$store = options.parent.$store;}}
}
在mvVuex.js中添加全局$store,实现自己的Vuex
// install方法会在外界调用Vue.use的时候执行,
//并且在执行的时候会把Vue实例和一些额外的参数传递出来
const install = (vm, options)=>{vm.mixin({// 在初始化$options之前,给每一个Vue实例添加一个$storebeforeCreate(){// Vue创建实例时会先创建根组件,再创建子组件,if(this.$options && this.$options.store){// 如果根组件,默认就有store,直接赋值即可this.$store = this.$options.store;}else{// 不是根组件,默认没有store,需要把父组件的$store赋值给它this.$store = this.$parent.$store;}}});
}
// 创建一个Store类,即初始结构
class Store {constructor(options){this.state = options;}
}
export default {install,Store
}
Vuex的state区别与全局变量的一点,就是state是响应式的,那么如何实现响应式的state呢?
// 官方示例中,是直接重写,用Object.defineProperties实现
Object.defineProperties( Store.prototype, prototypeAccessors$1 );
重写比较麻烦的话,可以直接利用new Vue()的时候,传入的data是响应式来实现
class Store {constructor(options){this.vm = new Vue({data:{state:options.state}})}// 自动触发get,转换this.vm,使得获取state时通过`this.$store.state`// 而不是`this.$store.vm.state`获取stateget state(){ return this.vm.state }
}
当然,也可以使用vue中自带的方法来实现响应式
class Store {constructor(options){Vue.util.defineReactive(this,'state',options.state)}
}
wrappedGetters(options){
let getters = options.getters || {};
this.getters = {};
for(let key in getters){Object.defineProperty(this.getters, key, {get:()=>{return getters[key](this.state);}})
}
}
store/index.js文件定义getters
// store/index.js
state: {name: 'HelloWorld'
},
getters: {myName(state){return '姓名:'+ state.name;}
},
App文件中使用
// App.vue{{this.$store.getters.myName}}
wrappedMutations(options){
let mutations = options.mutations || {};
this.mutations = {};
for(let key in mutations){this.mutations[key] = (payload)=>{ // 10mutations[key](this.state, payload); // addNum(this.state, 10);}}
}
// 再实现一个commit方法用来触发Mutation,需要用箭头函数,这里的this指向state
commit=(type, payload)=>{this.mutations[type](payload);
}
wrappedActions(options){
let actions = options.actions || {};
this.actions = {};
for(let key in actions){this.actions[key] = (payload)=>{actions[key](this, payload);}
}
}
// 再实现一个dispatch方法用来触发Actions,需要用箭头函数,这里的this指向state
dispatch=(type, payload)=>{this.actions[type](payload);
}
App.vue
{{this.$store.state.name}}
{{this.$store.getters.myName}}
{{this.$store.state.num}}
{{this.$store.state.age}}
store/index.js
import Vue from 'vue'
import Vuex from './myVuex'Vue.use(Vuex)export default new Vuex.Store({state: {name: 'HellowWorld',num:0,age:12},getters:{myName(state){return '姓名:'+ state.name;}},mutations: {addNum(state, payload){state.num += payload;},addAge(state, payload){state.age += payload;}},actions: {asyncAddAge({commit}, payload){setTimeout(()=>{commit('addAge', payload);}, 100);}}
})
myVuex.js
import Vue from 'vue'
// 公共方法
function forEachValue(obj,fn){Object.keys(obj).forEach(function (key) { return fn(obj[key], key); })
}
// install方法会在外界调用Vue.use的时候执行,
//并且在执行的时候会把Vue实例和一些额外的参数传递出来
const install = (vm, options)=>{vm.mixin({// 在初始化$options之前,给每一个Vue实例添加一个$storebeforeCreate(){// Vue创建实例时会先创建根组件,再创建子组件,if(this.$options && this.$options.store){// 如果根组件,默认就有store,直接赋值即可this.$store = this.$options.store;}else{// 不是根组件,默认没有store,需要把父组件的$store赋值给它this.$store = this.$parent.$store;}}});
}
// 创建一个Store类,即初始结构
class Store {constructor(options){Vue.util.defineReactive(this,'state',options.state)/*this.vm = new Vue({data:{state:options.state}})*/this.wrappedGetters(options);this.wrappedMutations(options)this.wrappedActions(options)}// 自动触发get,转换this.vm,使得获取state时通过`this.$store.state`// 而不是`this.$store.vm.state`获取state// get state(){ return this.vm.state }dispatch = (type, payload)=>{this.actions[type](payload);}wrappedActions(options){let actions = options.actions || {};this.actions = {};forEachValue(actions,(obj,key)=>{this.actions[key] = (payload) =>{obj(this,payload)}})}commit = (type, payload)=>{this.mutations[type](payload)}wrappedMutations(options){let mutations = options.mutations || {};this.mutations = {};forEachValue(mutations,(obj,key)=>{this.mutations[key] = (payload)=>{obj(this.state,payload)}})}wrappedGetters(options){let getters = options.getters || {};this.getters = {};forEachValue(getters,(obj,key)=>{Object.defineProperty(this.getters,key,{get:()=>{return obj(this.state)}})})}}
export default {install,Store
}
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module) 。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
https://vuex.vuejs.org/zh
命名空间
多个模块中不能出现同名的getters方法
多个模块的mutations、actions中可以出现同名的方法,多个同名的方法不会覆盖, 会放到数组中然后依次执行。
官方代码分析
// 自定义一个方法,根据规定的格式,来创建模块
var Module = function Module (rawModule, runtime) {this.runtime = runtime;this._children = Object.create(null); // 子模块this._rawModule = rawModule; // 当前模块var rawState = rawModule.state; // 当前模块的statethis.state = (typeof rawState === 'function' ? rawState() : rawState) || {};
};
......
var ModuleCollection = function ModuleCollection (rawRootModule) {// 传入模块信息,传入[]空数组,代表第一次传入,还没子模块的传入this.register([], rawRootModule, false);
};
.......
提取模块化结构,实现自己的module
let module = {_rawModule: rootModule,_state: rootModule.state,_children: {}
}
在myVuex.js新增一个类,单独处理module
class ModuleCollection {constructor(rootModule){this.register([], rootModule);}register(arr, rootModule){// console.log(arr); // [] [home] [login] [login,account]// 1.按照需要的格式创建模块let module = {_rawModule: rootModule,_state: rootModule.state,_children: {}}// 2.保存模块信息if(arr.length === 0){// 保存根模块,第一次传入一个[]空数组,即表示根this.root = module;}else{// 保存子模块,数组不为空,表示有子模块let parent = arr.splice(0, arr.length-1).reduce((root, currentKey)=>{return root._children[currentKey];}, this.root);parent._children[arr[arr.length-1]] = module;}// 3.处理子模块for(let childrenModuleName in rootModule.modules){let childrenModule = rootModule.modules[childrenModuleName];this.register(arr.concat(childrenModuleName) ,childrenModule)}}
}
constructor(options){Vue.util.defineReactive(this,'state',options.state)this.modules = new ModuleCollection(options);this.wrappedModules([], this.modules.root);
}
wrappedModules(arr, rootModule){if(arr.length > 0){let parent = arr.splice(0, arr.length-1).reduce((state, currentKey)=>{return state[currentKey];}, this.state);Vue.set(parent, arr[arr.length-1], rootModule._state);}// 入参由之前的options,变成为现在的模块中的_rawModulethis.wrappedGetters(rootModule._rawModule);this.wrappedMutations(rootModule._rawModule)this.wrappedActions(rootModule._rawModule)// 如果当前不是子模块, 那么就需要从根模块中取出子模块的信息for(let childrenModuleName in rootModule._children){let childrenModule = rootModule._children[childrenModuleName];this.wrappedModules(arr.concat(childrenModuleName), childrenModule);}
}
上面说到多个模块的mutations、actions中可以出现同名的方法
处理mutations、actions的同名方法,push到数组,逐个执行
dispatch = (type, payload)=>{this.actions[type].forEach(fn=>fn(payload));
}
wrappedActions(options){let actions = options.actions || {};this.actions = this.actions || {};for(let key in actions){this.actions[key] = this.actions[key] || [];this.actions[key].push((payload)=>{actions[key](this, payload);});}
}
commit = (type, payload)=>{this.mutations[type].forEach(fn=>fn(payload));
}
wrappedMutations(options){let mutations = options.mutations || {};this.mutations = this.mutations || {};for(let key in mutations){this.mutations[key] = this.mutations[key] || [];this.mutations[key].push((payload)=>{mutations[key](options.state, payload);});}
}
myVuex.js
import Vue from 'vue'
// 公共方法
function forEachValue(obj,fn){Object.keys(obj).forEach(function (key) { return fn(obj[key], key); })
}
// install方法会在外界调用Vue.use的时候执行,
//并且在执行的时候会把Vue实例和一些额外的参数传递出来
const install = (vm, options)=>{vm.mixin({// 在初始化$options之前,给每一个Vue实例添加一个$storebeforeCreate(){// Vue创建实例时会先创建根组件,再创建子组件,if(this.$options && this.$options.store){// 如果根组件,默认就有store,直接赋值即可this.$store = this.$options.store;}else{// 不是根组件,默认没有store,需要把父组件的$store赋值给它this.$store = this.$parent.$store;}}});
}
class ModuleCollection {constructor(rootModule){this.register([], rootModule);}register(arr, rootModule){console.log(arr)// 1.按照我们需要的格式创建模块let module = {_rawModule: rootModule,_state: rootModule.state,_children: {}}// 2.保存模块信息if(arr.length === 0){// 保存根模块,第一次传入一个[]空数组,即表示根this.root = module;}else{// 保存子模块,数组不为空,表示有子模块let parent = arr.splice(0, arr.length-1).reduce((root, currentKey)=>{return root._children[currentKey];}, this.root);console.log(parent);parent._children[arr[arr.length-1]] = module;}// // 3.处理子模块for(let childrenModuleName in rootModule.modules){let childrenModule = rootModule.modules[childrenModuleName];this.register(arr.concat(childrenModuleName) ,childrenModule)}}
}
// 创建一个Store类,即初始结构
class Store {constructor(options){Vue.util.defineReactive(this,'state',options.state)/*this.vm = new Vue({data:{state:options.state}})*/this.modules = new ModuleCollection(options);this.wrappedModules([], this.modules.root);}wrappedModules(arr, rootModule){if(arr.length > 0){let parent = arr.splice(0, arr.length-1).reduce((state, currentKey)=>{return state[currentKey];}, this.state);Vue.set(parent, arr[arr.length-1], rootModule._state);}this.wrappedGetters(rootModule._rawModule);this.wrappedMutations(rootModule._rawModule)this.wrappedActions(rootModule._rawModule)// 如果当前不是子模块, 那么就需要从根模块中取出子模块的信息来安装for(let childrenModuleName in rootModule._children){let childrenModule = rootModule._children[childrenModuleName];this.wrappedModules(arr.concat(childrenModuleName), childrenModule);}}// 自动触发get,转换this.vm,使得获取state时通过`this.$store.state`// 而不是`this.$store.vm.state`获取state// get state(){ return this.vm.state }dispatch = (type, payload)=>{this.actions[type].forEach(fn=>fn(payload));}wrappedActions(options){let actions = options.actions || {};this.actions = this.actions||{};forEachValue(actions,(obj,key)=>{this.actions[key] = this.actions[key] || [];this.actions[key].push((payload)=>{obj(this, payload);});})}commit = (type, payload)=>{this.mutations[type].forEach(fn=>fn(payload));}wrappedMutations(options){let mutations = options.mutations || {};this.mutations = this.mutations || {};forEachValue(mutations,(obj,key)=>{this.mutations[key] = this.mutations[key] || [];this.mutations[key].push((payload)=>{obj(options.state, payload);});})}wrappedGetters(options){let getters = options.getters || {};this.getters = this.getters || {};forEachValue(getters,(obj,key)=>{Object.defineProperty(this.getters,key,{get:()=>{return obj(this.state)}})})}}
export default {install,Store
}
store/index.js
import Vue from 'vue'
// import Vuex from 'vuex'
import Vuex from './myVuex'Vue.use(Vuex)
let home = {state: {name: 'home',globalNum:10},getters: {// 多个模块中不能出现同名的getters方法getHomeName(state){return state.name + '子模块Home';},},mutations: {changeHomeName(state, payload){state.name += payload;},// 多个模块的mutations中可以出现同名的方法// 多个同名的方法不会覆盖, 会放到数组中然后依次执行changeGlobalNum(state, payload){state.globalNum += payload;}},actions: {// 多个模块的actions中可以出现同名的方法// 多个同名的方法不会覆盖, 会放到数组中然后依次执行asyncChangeGlobalNum({commit}, payload){console.log('home中的asyncChangeGlobalNum');setTimeout(()=>{commit('changeGlobalNum', payload);}, 100);}}
}
let account = {state: {name: 'account',globalNum:10},getters: {// 多个模块中不能出现同名的getters方法getAccountName(state){return state.name + '子模块Account';},},mutations: {changeAccountName(state, payload){state.name += payload;},// 多个模块的mutations中可以出现同名的方法// 多个同名的方法不会覆盖, 会放到数组中然后依次执行changeGlobalNum(state, payload){state.globalNum += payload;}},actions: {// 多个模块的actions中可以出现同名的方法// 多个同名的方法不会覆盖, 会放到数组中然后依次执行asyncChangeGlobalNum({commit}, payload){console.log('Account中的asyncChangeGlobalNum');setTimeout(()=>{commit('changeGlobalNum', payload);}, 100);}}
}
let login = {state: {name: 'login',globalNum:10},getters: {// 多个模块中不能出现同名的getters方法getLoginName(state){return state.name + '子模块Login';},},mutations: {changeLoginName(state, payload){state.name += payload;},// 多个模块的mutations中可以出现同名的方法// 多个同名的方法不会覆盖, 会放到数组中然后依次执行changeGlobalNum(state, payload){state.globalNum += payload;}},actions: {// 多个模块的actions中可以出现同名的方法// 多个同名的方法不会覆盖, 会放到数组中然后依次执行asyncChangeGlobalNum({commit}, payload){console.log('Login中的asyncChangeGlobalNum');setTimeout(()=>{commit('changeGlobalNum', payload);}, 100);}},modules: {account:account}
}
export default new Vuex.Store({// 用于保存全局共享数据state: {name: 'HelloWorld',globalNum:10,num:0,age:12},getters:{myName(state){return state.name + '111';}},// 用于同步修改共享数据mutations: {addNum(state, payload){state.num += payload;},addAge(state, payload){state.age += payload;},changeGlobalName(state, payload){state.globalNum += payload;}},// 用于异步修改共享数据actions: {asyncChangeGlobalNum({commit}, payload){console.log('全局中的asyncChangeGlobalNum');setTimeout(()=>{commit('changeGlobalNum', payload);}, 100);}},// 用户模块化共享数据modules: {home:home,login:login}
})
App.vue
父组件:{{this.$store.state.name}}
Home子组件:{{this.$store.state.home.name}}
Login子组件:{{this.$store.state.login.name}}
Login中的Account子组件:{{this.$store.state.login.account.name}}