vuex作为一个共享的数据对象,用于不同组件的共用一个数据,并且动态响应数据变化,配合computed属性,可以对数据进行缓存。

在vuex里面,在异步处理actions上,最终也还是要用到commit同步操作方法操作数据,并不是actions方法本身处理了数据,原因是因为需要有操作记录,方便在插件上查看。

废话不多说,直接上底层原理。

原理

vuex其实就是一个重新封装的new Vue 对象,他的动态响应数据就是data属性,而commit这些方法,都只是回调而已。

javascript
复制代码
import Vue from "vue"; const Store = function(options = {}) { const { state = {}, mutations = {} } = options; this._vm = new Vue({ data: { $$state: state }, }); this._mutations = mutations; }; Store.prototype.commit = function(type, value) { if (this._mutations[type]) { this._mutations[type](this.state, value); } }; Object.defineProperties(Store.prototype, { state: { get: function() { return this._vm._data.$$state } } }); export default { Store };

引入一个vue对象,然后创建一个Store的构造函数,并接收一个参数options,options设置一个默认空对象。

Store构造函数内部我们解构这个options,获取到state和mutations对象,如果没有,我们也默认是一个空对象。

给Store构造函数的this,也就是prototype增加一个_vm属性,该属性是一个new Vue对象,并且在这个vue的data属性上,我们使用$$state的key,其属性值指向了之前解构的state对象。

这里我们就将数据给监听了,利用vue的特性。

现在我们需要注册mutations对象,mutations本身就是同步操作state属性的方法的一个对象集合。

直接this._mutations = mutations,将解构的mutations挂载到Store的prototype上。

创建commit方法

commit方法用于统一调用mutations中的对应的方法,我们看个vuex的例子:

javascript
复制代码
this.$store.commit("方法名",value);

所以,我们也要给Store构造函数添加一个commit方法,这个方法接收两个参数,一个是要调用的方法名,一个是值。

javascript
复制代码
Store.prototype.commit = function(type, value) { if (this._mutations[type]) { this._mutations[type](this.state, value); } };

然后我们if判断,如果this._mutations中存在这个方法,我们就调用和这个方法,并且把state和value值传过去。

注意

此时我们的this.state是没有的,你可以仔细往上看,在添加commit方法的时候,我们还没有对this这个对象,也就是Store本身添加一个state属性。

defineProperties配置一个state属性

defineProperties是defineProperty的加强版,defineProperty一次只能修改或者创建一个属性,defineProperties可以多个。

javascript
复制代码
Object.defineProperties(Store.prototype, { state: { get: function() { return this._vm._data.$$state } } });

defineProperties接收两个参数,第一个要定义的对象,第二个是定义的参数。

定义一个state属性,他的get方法返回this._vm._data下的$$state属性。

在vue对象里面,data对象是挂载的_data下的。

使用defineProperties定义的属性,是无法被for in遍历出key值得,并且我们配置set方法,所以这个state也无法被改写。

你无法Store.state = {}去改变state的指向,或者设置他的值,都不行。

然后我们导出这个State对象。

因为vuex的使用一般是这样:

javascript
复制代码
new Vuex.Store()

所以我们也导出一个对象,对象里面包含Stote

这样我们在使用时:

javascript
复制代码
import miniVuex from "./mini-vuex"; const miniStore = new miniVuex.Store({}); Vue.prototype.$miniStore = miniStore;

基本保持一致。

例子

javascript
复制代码
import miniVuex from "./mini-vuex"; const miniStore = new miniVuex.Store({ state: { test: "我是一个测试的值" }, mutations: { setTest(state, value) { state.test = value; } } }); Vue.prototype.$miniStore = miniStore;

这样我们可以通过this.$miniStore来使用。

配置getters

首先我们要知道,getters一般是怎么用的。

javascript
复制代码
const miniStore = new miniVuex.Store({ state: { test: "我是一个测试的值" }, getters:{ test(state,getters){ return state.test; } }, mutations: { setTest(state, value) { state.test = value; } } });

getters是一个对象,对象里面的方法,接收两个参数,一个是state,一个是getters,state就不用多说了,而getters获取的是getters对象本身,用户获取其他值。

而我们之前说过,getters实际上会使用conputed缓存,所以我们需要在miniVuex中,解构到这个getters,然后for循环遍历出里面的内容,并绑定到computed上。

javascript
复制代码
const Store = function Store(options = {}) { const {state = {}, mutations={}, getters={}} = options; const computed = {} const store = this; store.getters = {}; for (let [key, fn] of Object.entries(getters)) { computed[key] = function () { return fn(store.state, store.getters); }; }; this._vm = new Vue({ data: { $$state: state }, computed, }) this._mutations = mutations };

这里有几个关键的点:

Object.entries是一个对象的方法,他会将对象里面的键值对转换为数组,例:

javascript
复制代码
const obj = {a:1,b:2}; console.log(Object.entries(obj)); //输出:[[a,1],[b,2]]

然后我们for of循环这个数组,该遍历方法是es6的方法,他会创建一个用于每次遍历的对象,如果是数组的话,第一个对象就是对应数组下标0.

然后使用解构,我们将key和fn解析出来,fn实际上就是创建vuex是,getters中我们预先写好的方法。

然后我们将它挂载到computed空对象上面,这里使用了一个工厂函数抛出,如果不使用工厂函数,这里我们首先要传入两个参数(state,getters),并且return出一个值,这就导致运行完毕后直接return,后面的函数就不执行了。所以我们需要一个函数包裹,这样return停止的是这个工厂函数,外面的代码依旧可以运行。

传入的两个参数

我们需要传入state和getters。

store的话可以通过this.store获取,但是在函数里面this的指向会发生变化,所以我们创建一个变量store来指向这个this

于是const store = this;,第一个参数为store.state

而getters暂时还没有,所以我们需要手动创建,于是:store.getters = {};,第二个参数store.getters

这个时候,我们已经将getters挂载到Store这个构造函数对象上面,而computed挂载到this._vm.computed上,而vue会将computed中的属性全都挂载到根对象上,所以我们可以直接通过this._vm.test获取到数据。

但是这个时候并没有达到我们的要求,我们的getter的调用方法是这样的:

javascript
复制代码
this.$store.getters.test

目前的调用方式是:miniVuex._vm.test

所以我们还需要改写store.getters的get方法。

使用Object.defineProperty我们将对象的get方式改变一下

javascript
复制代码
for (let [key, fn] of Object.entries(getters)) { computed[key] = function () { return fn(store.state, store.getters); }; //改写get Object.defineProperty(store.getters, key, { get: function () { return store._vm[key]; }, }); };

在for循环里面,我们对store.getters对象,创建一个key,而这个key直接返回store._vm[key],store._vm=vue根对象,所以实际上这里我们return的是computed的值,缓存目的达到。

这个时候:

javascript
复制代码
miniStore.getters.test

是有值得了。

这样我们的getters就写完了。

挂载到vue对象上

先上源码:

javascript
复制代码
let Vue; function install(_Vue) { Vue = _Vue; function vuexInit() { var options = this.$options; // store injection 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; } } Vue.mixin({ beforeCreate: vuexInit }); }

我只知道,Vue.use()会默认执行js里面导出的install方法,然后将Vue对象作为参数传入install中

在这里,我们给vue的mixin全局混入,在每个创建vue对象之前beforeCreate,触发vuexInit方法。

vuexInit中this就是这个组件实例,然后他的options就是配置,如:

javascript
复制代码
new Vue({ store, render: h => h(App), }).$mount('#app')
javascript
复制代码
{ store, render: h => h(App), }

就是options,然后判断options上有没有store对象,如果有的话,就给this
创建一个$store属性,然后他的值做一个三元判断,有可能这个vuex并没有初始化,所以他是一个方法,我们就赋值这个运行后的方法,然后如果不是一个方法,说明store是初始化好的,我们直接赋值。

这样这个组件就能通过this.$store获取store。

这个情况是针对第一个vue组件的,而其他的子组件,我们就要通过options.parent获取到父组件,然后判断父组件有没有store,有的话就this.$store = options.parent.$store;

然后我没有看过vue的源码,但是根据这个情况,我判断vue的组件渲染他是有顺序的,所以这里总能从父组件获取到store,然后这样不断的传递下去,父传子,子传孙,然后他们都是同一个vuex对象。

完成品

main.js调用

javascript
复制代码
import Vue from 'vue' import Vuex from './min-vuex' import App from './App.vue' Vue.use(Vuex) Vue.config.productionTip = false const store = new Vuex.Store({ state: { count: 0, }, mutations: { increment(state) { state.count++ } }, getters: { doubleCount(state) { return state.count * 2 } } }) new Vue({ store, render: h => h(App), }).$mount('#app')

miniVuex

javascript
复制代码
let Vue; function install(_Vue) { Vue = _Vue; function vuexInit() { var options = this.$options; // store injection 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; } } Vue.mixin({ beforeCreate: vuexInit }); } const Store = function Store(options = {}) { const { state = {}, mutations = {}, getters = {} } = options const computed = {} const store = this store.getters = {}; for (let [key, fn] of Object.entries(getters)) { computed[key] = function() { return fn(store.state, store.getters); }; Object.defineProperty(store.getters, key, { get: function() { return store._vm[key]; }, }); } this._vm = new Vue({ data: { $$state: state }, computed, }) this._mutations = mutations } Store.prototype.commit = function(type, payload) { if (this._mutations[type]) { this._mutations[type](this.state, payload) } } Object.defineProperties(Store.prototype, { state: { get: function() { return this._vm._data.$$state } } }); export default { Store, install }
分类: vue 开发实战(完结) 标签: vuevuexdefineProperties

评论

暂无评论数据

暂无评论数据

目录