vuex的实现
源代码可以查看我的github: https://github.com/Jasonwang911/TryHardEveryDay/tree/master/Vuex/vuex-resouce 欢迎star
先描述一下vuex的使用:
store.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import Vue from "vue" ; import Vuex from "./vuex/myVuex" ; Vue.use(Vuex); export default new Vuex.Store({ state: { age: 20 }, getters: { // date 的 computed myAge(state) { return state.age + 10; } }, mutations: { syncAdd(state, payload) { state.age += payload; }, syncMinuts(state, payload) { state.age -= payload; } }, actions: { asyncMinus({ commit }, payload) { setTimeout(() => { commit( "syncMinuts" , payload); }, 1000); } } }); |
main.js中引用:
1 2 3 4 5 6 7 8 9 10 11 12 | import Vue from "vue" ; import App from "./App.vue" ; import router from "./router" ; import store from "./store" ; Vue.config.productionTip = false ; new Vue({ router, store, // 在每个实例上添加一个 $store 对象 render: h => h(App) }).$mount( "#app" ); |
先说这两点
1. Store是一个类
2. Vue.use(Vuex);
首先建立一个类并导出,同时导出一个install的方法,这个方法接收一个参数 _Vue,即 vue实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | let Vue class Store { } const install = _Vue => { Vue = _Vue } export default { Store, install } |
然后再main.js 中所作的就是讲store实例注册到vue的跟组件和所有的子组件上, 这里使用了Vue.mixin()的方法,在每个组件中注入$store,并在子组件中通过 this.$parents来拿到父组件的$store并添加到子组件的实例上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | const install = _Vue => { Vue = _Vue; // console.log(Vue); Vue.mixin({ beforeCreate() { if ( this .$options && this .$options.store) { // 根组件 this .$store = this .$options.store; } else { this .$store = this .$parent && this .$parent.$store; } } }); }; |
先来看看state是怎么用的
1 2 3 4 5 6 7 8 9 10 | import Vue from "vue" ; import Vuex from "./vuex/myVuex" ; Vue.use(Vuex); export default new Vuex.Store({ state: { age: 20 }, }); |
在new Vuex.Store的实例的时候传入了一个对象,这个对象options
然后我们添加state的这个属性
class Store { constructor(options) { // options 是 new Vuex时注入的对象 this._vm = options.state || {}; } get state() { // store.state return this._vm; } }
这里有一个问题,通过options获取的state并不是Observe状态的,并不能响应式的更新视图,如何做才能使state变成响应式的并触发视图的更新呢,也就是通过Vue的数据劫持进行包装转换,
这里通过new Vue,并把state放入创建的Vue的data中来实现这个操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Store { constructor(options) { // options 是 new Vuex时注入的对象 this ._vm = new Vue({ data: { state: options.state } }); } get state() { // store.state return this ._vm.state; } } |
这样,$store.state就变成了响应式的状态了
接下来,同理 getters,mutations, actions 也是通过options传入获取的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import Vue from "vue" ; import Vuex from "./vuex/myVuex" ; Vue.use(Vuex); export default new Vuex.Store({ state: { age: 20 }, getters: { // date 的 computed myAge(state) { return state.age + 10; } }, mutations: { syncAdd(state, payload) { state.age += payload; }, syncMinuts(state, payload) { state.age -= payload; } }, actions: { asyncMinus({ commit }, payload) { setTimeout(() => { commit( "syncMinuts" , payload); }, 1000); } } }); |
先来实现一下getters方法: 每一个getter方法其实都是getters的一个属性,但是这个属性返回了一个函数。属性可以返回函数的方法可以通过Object.definedProperty()中的get()来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Store { constructor(options) { // options 是 new Vuex.Store时注入的对象 // getters 拿到new Store的时候传入的getters let getters = options.getters || {}; // 创建 Store 的 getters 属性 this .getters = {}; Object.keys(getters).forEach(getterName => { // 是个函数,但是返回的是个属性 Object.defineProperty( this .getters, getterName, { get () { return getters[getterName]( this .state); } }); }); } } |
这里其实只做了一件事情: 循环传入的options上的getters对象,并在用户调用getters中的每一个属性的时候返回一个getter函数并执行,
同理,mutations和actions也是如此实现的,只不过各自多了一个commit和dispatch的方法。
commit 和 dispatch 方法的原理就是通过参数type去对应的mutations和actions中查找对应的mutation或者action传入payload并执行
1 2 3 4 5 6 | dispatch = (type, payload) => { this .actions[type](payload); }; commit = (type, payload) => { this .mutations[type](payload); }; |
查看Vuex源码,作者巧妙的将这个通过对象key进行循环的方法封装成了一个forEach函数:
1 2 3 4 5 | const forEach = (obj, callback) => { Object.keys(obj).forEach(key => { callback(key, obj[key]); }); }; |
这个函数传入了一个对象和一个回调函数,通过对key的循环,执行了回调函数,并向回调函数中传入了循环出对象obj的key和value,整理后整个Store对象变成了如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | const forEach = (obj, callback) => { Object.keys(obj).forEach(key => { callback(key, obj[key]); }); }; class Store { constructor(options) { // options 是 new Vuex时注入的对象 // this._s = options.state || {}; // 将store中的状态变成可被观测的数据 this ._vm = new Vue({ data: { state: options.state } }); // getters 拿到new Store的时候传入的getters let getters = options.getters || {}; // 创建 Store 的 getters 属性 this .getters = {}; forEach(getters, (getterName, fn) => { Object.defineProperty( this .getters, getterName, { get : () => { return fn( this .state); } }); }); // mutations let mutations = options.mutations || {}; this .mutations = {}; forEach(mutations, (mutationName, fn) => { this .mutations[mutationName] = payload => { fn( this .state, payload); }; }); // actions let actions = options.actions || {}; this .actions = {}; forEach(actions, (actionName, fn) => { this .actions[actionName] = payload => { fn( this , payload); }; }); } dispatch = (type, payload) => { this .actions[type](payload); }; commit = (type, payload) => { this .mutations[type](payload); }; get state() { return this ._vm.state; } } |
以上基本实现了一个简单的vuex的基本流程,所有的源码可以在我的github上查看,后续会继续修改支持vuex的模块化。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步