vuex核心概念和底层原理
vuex作为一个共享的数据对象,用于不同组件的共用一个数据,并且动态响应数据变化,配合computed属性,可以对数据进行缓存。
在vuex里面,在异步处理actions上,最终也还是要用到commit同步操作方法操作数据,并不是actions方法本身处理了数据,原因是因为需要有操作记录,方便在插件上查看。
废话不多说,直接上底层原理。
原理
vuex其实就是一个重新封装的new Vue 对象,他的动态响应数据就是data属性,而commit这些方法,都只是回调而已。
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的例子:
this.$store.commit("方法名",value);
所以,我们也要给Store构造函数添加一个commit方法,这个方法接收两个参数,一个是要调用的方法名,一个是值。
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可以多个。
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的使用一般是这样:
new Vuex.Store()
所以我们也导出一个对象,对象里面包含Stote
这样我们在使用时:
import miniVuex from "./mini-vuex";
const miniStore = new miniVuex.Store({});
Vue.prototype.$miniStore = miniStore;
基本保持一致。
例子
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一般是怎么用的。
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上。
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是一个对象的方法,他会将对象里面的键值对转换为数组,例:
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的调用方法是这样的:
this.$store.getters.test
目前的调用方式是:miniVuex._vm.test
所以我们还需要改写store.getters的get方法。
使用Object.defineProperty
我们将对象的get方式改变一下
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的值,缓存目的达到。
这个时候:
miniStore.getters.test
是有值得了。
这样我们的getters就写完了。
挂载到vue对象上
先上源码:
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就是配置,如:
new Vue({
store,
render: h => h(App),
}).$mount('#app')
{
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调用
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
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
}
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
暂无评论数据