Vuex源码分析,实现简易版的Vuex
admin
2024-01-29 06:52:13
0

vuex是什么?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
https://vuex.vuejs.org/zh/

vuex 是 Vue 配套的公共数据管理库,它可以把一些共享的数据,保存到 vuex 中,方便整个程序中的任何组件直接获取或修改我们的公共数据。使用Vuex的时候需要用到Vue的use方法,因此它的本质是一个插件。

Vuex结构

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。

添加全局$store

把官方的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
}

实现响应式state

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)}
}

实现Getters

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

实现Mutation

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);
}

实现Actions

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


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
}

module模块化处理

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module) 。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
https://vuex.vuejs.org/zh

命名空间

多个模块中不能出现同名的getters方法

多个模块的mutationsactions中可以出现同名的方法,多个同名的方法不会覆盖, 会放到数组中然后依次执行。

官方代码分析

// 自定义一个方法,根据规定的格式,来创建模块
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: {}
}

实现module模块化

在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);}
}

上面说到多个模块的mutationsactions中可以出现同名的方法

处理mutationsactions的同名方法,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


相关内容

热门资讯

A股异动丨避险情绪升温促金银价... A股市场黄金股强势,其中,四川黄金、招金黄金涨停,山金国际涨超6%,中金黄金、西部黄金涨超4%。 消...
服刑7年获判无罪,女商人林惠荣... 澎湃新闻记者 王选辉 1月19日,澎湃新闻从福建女商人林惠荣处了解到, 其向法院申请的国家赔偿,经漳...
康骨堂 骨质增生怎么保养 骨质增生(骨关节炎)可以通过多种方式进行保养和治疗,常见方法包括生活方式调整、物理疗法、非甾体抗炎药...
原创 中... 1月15日,中银基金发布公告称,联席投资总监(固定收益)邢科因个人原因离任。 公开资料显示,邢科于...
商汤在厦门成立智能开发公司 注... 天眼查工商信息显示,1月19日,厦门商汤智能开发有限公司成立,法定代表人为马堃,注册资本3000万美...
马斯克:AI5芯片设计基本完成... 近期,特斯拉CEO埃隆·马斯克密集披露芯片战略相关信息,这表明特斯拉正在调整其AI芯片与超级计算平台...
“俩西红柿要10块钱”?专家:... “俩西红柿要10块钱”“鸡蛋都快配不上西红柿啦”……近段时间,全国多地西红柿价格明显上涨,引发市场高...
圣贝拉集团今日股价涨超12%,... 1月19日,全球家庭护理行业龙头圣贝拉集团(股票代码:2508.HK)发布公告,正式宣布与服务机器人...
刘世锦:建议实施进出口基本平衡... 1月17日,中国环境与发展国际合作委员会中方首席顾问、国务院发展研究中心原副主任刘世锦在第二十七届北...
阳光电源逐光出海,锚定全球储能... 全球能源转型浪潮澎湃,储能作为构建新型电力系统的关键支撑,其战略地位日益凸显。在这场关乎未来能源格局...
封关整一个月,海南卖爆了 1 2025年12月18日,海南自贸港全岛正式封关。 今天是2026年1月19日,海南封关整整一个月...
容积率下调,体量23万㎡!福州... 海西房产网(微信公众号:fjhxfcw)消息:为传承弘扬“中国福州国际招商月”思想精髓,福州市招商办...
比格比萨赴港IPO,创始人赵志... 瑞财经 严明会 1月16日,比格餐饮国际控股有限公司(以下简称:比格比萨)递交招股书,冲击港股IPO...
罗永浩被禁言后现身颁奖典礼,领... 1月19日,罗永浩于谦拒领终身成就奖的相关话题登上热搜。 1月18日,在B站年度百大UP主颁奖典礼...
原创 G... 如果说欧盟是个戏班子,每次开会就像上演一场戏,那G7就更像是个马戏团,所有的成员都是戏精上身,开会时...
原创 赚... 最近,关于俄罗斯暂停对中国电力出口的消息引发了广泛的讨论。西方媒体迅速抓住这个机会,宣称中俄合作出现...
原创 指... 现阶段的市场已经进入到“降温”的调整时期,针对A股“易跌难涨”的特性,这个位置千万不要“空手接白刃”...