Vuex原理解析
Vuex是基于Vue的响应式原理基础,所以无法拿出来单独使用,必须在Vue的基础之上使用。
1.Vuex使用相关解析
main.js
1 import store form './store' // 引入一个store文件 2 3 new Vue({ 4 // 在Vue初始化的过程中,注入一个store属性,内部会将这个属性放到每个组件的$store上 5 store, 6 })
store.js
1 import Vuex from 'Vuex' 2 3 Vue.use(Vuex) 4 5 // 通过Vuex中的一个属性 Store 创建一个store的实例 6 export default new Vuex.Store({ 7 state: { // 单一数据源 8 age: 10 9 }, 10 mutations: { // 11 // payload 载荷 12 syncChange(state,payload) { // 修改状态的方法 同步更改 13 state.age+= payload 14 } 15 }, 16 actions: { 17 asyncChange({commit}, payload) { 18 setTimeout(() => { 19 commit('syncChange',payload) 20 },1000) 21 } 22 } 23 }) 24 //mutations中增加异步操作 严格模式下会直接报错,普通模式下不会报错但不合法
2.Vuex原理解析实现
首先我们要清楚Vuex的定位,它是一个插件。且必须基于之上Vue来使用,为什么这么说呢,因为他的数据响应是基于Vue的。
1.Vuex核心概念
① state 驱动应用的数据源。
② Getter getter的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生变化了改变才会被重新计算(由此你是不是想到了计算属性,对完全可以这么理解)。
③ Mutation 进行Vuex中store状态的更改,也是官方规定更改状态的唯一途径。
④ Action 进行异步操作的场所,但是更改数据还是需要commit提交。
⑤ Module 单一状态树对象比较复杂,Vuex允许我们将Store分割成多模块,每个模块拥有自己独立的内容。
2.实现Vuex
store.js
先创建一个入口文件
1 import Vue from 'vue' 2 // import Vuex from 'vuex' 官方的Vuex插件 3 import Vuex from './vuex/index1' // 自己写的Vuex 4 5 Vue.use(Vuex) // 默认会执行当前插件的install方法 6 7 // 通过Vuex中的一个属性 Store 创建一个store的实例 8 export default new Vuex.Store({ 9 // 定义数据 10 modules: { 11 a: { 12 state: { 13 age: 'a100' 14 }, 15 mutations: { 16 syncChange() { 17 console.log('a'); 18 } 19 }, 20 }, 21 b: { 22 state: { 23 age: 'b100' 24 }, 25 mutations: { 26 syncChange() { 27 console.log('b'); 28 } 29 }, 30 modules: { 31 c: { 32 state: { 33 age: 'c100' 34 }, 35 mutations: { 36 syncChange() { 37 console.log('c'); 38 } 39 }, 40 } 41 } 42 } 43 }, 44 state: { 45 age: 10 46 }, 47 mutations: { 48 syncChange(state, payload) { 49 state.age += payload 50 } 51 }, 52 actions: { 53 asyncChange({ commit }, payload) { 54 setTimeout(() => { 55 commit('syncChange', payload) 56 }, 1000) 57 } 58 }, 59 getters: { 60 myAge(state) { 61 return state.age + 20 62 } 63 } 64 })
index1.js
这边会暴露出一个install方法,Vue.sue()的时候会调用它。还有一个提供实例化的Store类
1 let vue 2 const install = (_Vue) => { 3 Vue = _Vue // install方法调用时,会将Vue作为参数传入 4 5 Vue.mixin({ // 全局注册一个混入,影响注册以后的每一个创建的Vue实例。 6 beforeCreate() { 7 // 判断当前根实例上有没有store,有的话把根组件的的store属性 放到每个组件的实例上 8 // 这样每个组件上都能直接实现this.$store去访问store里面的东西 9 if(this.$option.store) { 10 this.$store = this.$options.store 11 } else { 12 this.$store = this.$parent && this.$parent.$store 13 } 14 } 15 }) 16 } 17 18 19 class Store { // 用户获取的是这个store类的实例 20 constructor(options) { 21 // 创建Vue的实例 保证更新状态可以刷新视图 22 this.vm = new Vue({ 23 data: { 24 state: optons.state 25 } 26 }) 27 } 28 29 // es6 的访问器 30 get state() { 31 return this.vm.state 32 } 33 34 this.getters = {} 35 this.mutations = {} 36 this.actions = {} 37 // 1、需要将用户传入的数据进行格式化操作 38 this.moudules = new ModulesCollections(options) 39 40 // 2、递归的安装模块 41 installModule(this,this.state,[], this.modules.root) 42 43 // 调用 44 commit = (mutationName, payload) => { 45 // es7写法 这个里面的this 永远指向当前的store实例 46 this.mutaions[mutationName].forEach(fn =>fn(payload)) 47 } 48 49 dispath = (actionName, payload) => { 50 this.actions[actionName].forEach(fn =>fn(payload)) 51 } 52 } 53 54 // 定义一个forEach遍历当前对象属性然后执行一个回调函数,后面要用到 55 const forEach = (obj, callback) => { 56 Object.keys(obj).forEach(key => { 57 callback(key, obj[key]) 58 }) 59 } 60 61 // 格式化用户数据 62 class ModuleCollection { 63 constructor(options) { 64 // 深度将所有的子模块都遍历一遍 65 this.register([], ooptions) 66 } 67 register(path, rootModule) { 68 let rawModule = { 69 _raw: rootModule, 70 _children: {}, 71 state: rootModule.state 72 } 73 if(!this.root) { 74 this.root = rawModule 75 } else { 76 // 找到要定义的模块,将这个模块定义他父亲的_children属性里 77 let parentModule = path.slice(0,-1).reduce((root, current) => { 78 return root._children[current] 79 }, this.root) 80 parentModule._childen[path[path.length - 1]] = rawModule 81 } 82 83 // 如果有子模块 84 if(rootModule.modules) { 85 forEach(rootModule.modules,(moduleName, module) => { 86 this.register(path.concat(moduleName), module) 87 }) 88 } 89 } 90 } 91 92 // 递归安装模块 93 function installModule(store, rootState, path, rawModeule) { 94 // 如果有子模块,安装子模块的状态 95 if(path.length > 0) { 96 let parentState = path.slice(0,-1).reduce((root, current) => { 97 return root[current] 98 }, rootState) 99 Vue.set(parentState, path[path.length -1],rawModule.state) 100 } 101 102 let getters = rawModule._raw.getters // 取用户的getter 103 if(getters) { 104 forEach(getters, (getterName, value) => { 105 Object.defineProperty(store.getters, getterName, { 106 get: () => { 107 return value(rawModule.state) 108 } 109 }) 110 }) 111 } 112 113 let mutations = rawModule.raw.mutations // 取用户的mutation 114 if(mutations) { 115 forEach(mutations, (mutationName, value) => { 116 let arr = store.mutations[mutationName] || (store.mutaons[mutationName] = []) 117 }) 118 arr.push((plyload) => { 119 value(rawModule.state, payload) 120 }) 121 } 122 123 let actions = rawModule._raw.actions // 取用户的action 124 if(actions) { 125 forEach(actions, (actionName, value) => { 126 let arr = store.actions[actionName] || (store.actions[actionName] = []) 127 arr.push((payload) => { 128 value(store, payload) 129 }) 130 }) 131 } 132 133 // 递归 134 forEach(rawModule._childen, (moduleName, rawModule) => { 135 installModule(store, rootState, path.concat(moduleName),rawModule) 136 }) 137 }
3.实现步骤总结:
1、作为插件引入,执行install方法调用Vue.mixin在Vue全局生命周期混入一个方法,将Vuex中定义的数据源挂载到this.$store,即当前组件的实例上。
2、state 直接new Vue实例,将数据源传入。完成数据源响应式操作。
3、getters 递归遍历用户传入的getters对象,拿到每个里面每一个函数,通过Object.definedProperty属性处理。当get函数读取compile,触发get调用相应函数(函数内部自动传入当前数据源state作为参数),完成数据响应。
4、mutations 递归遍历用户传入的mutations 对象,将相同名称下的函数都挂载到当前实例的mutations数组中,完成订阅。commit的时候拿到对应的函数名称进行遍历mutations数组调用对应名称函数,完成发布。
5、actiosns 操作和mutations一样。
6、module 是将用户传入的数据进行格式化,格式化好以后执行上面的安装模块的方法。具体查看上方installModule方法的详细操作。