您的位置:首页 > Web前端 > Vue.js

知识体系:Vuex的实现原理

2020-08-10 20:08 786 查看

Vuex作为vue的好基友,还是有必要看一下他的实现原理的,我依旧是写一个例子,用debugger的形式探索Vuex的奥秘

store.js

import Vue from "vue"
import Vuex from "vuex"

Vue.use(Vuex)

const state = {
name:"yjt",
age:"18"
}

const mutations = {
setInfo(state, obj){
state.name = obj
}
}

const actions = {
setInfo({ commit }, obj){
commit('setInfo', obj)
}
}

export default new Vuex.Store({
state,
mutations,
actions,
strict:true
})

首先注册插件,就会进入Vue.use

Vue.use = function (plugin) {
var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
if (installedPlugins.indexOf(plugin) > -1) {
return this
}

// additional parameters
var args = toArray(arguments, 1);
args.unshift(this);
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args);
} else if (typeof plugin === 'function') {
plugin.apply(null, args);
}
installedPlugins.push(plugin);
return this
};

Vue会判断你传进来的是函数还是对象,如果是函数就直接执行,如果是对象就执行他的install函数
而我们的Vuex是有install函数的,并且把Vue对象当作参数传入
我们看下Vuex的install函数

function install (_Vue) {
if (Vue && _Vue === Vue) {
if ((process.env.NODE_ENV !== 'production')) {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
);
}
return
}
Vue = _Vue;
applyMixin(Vue);
}

很明显他会执行 applyMixin(Vue);这个函数

function applyMixin (Vue) {
var version = Number(Vue.version.split('.')[0]);

if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit });
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
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);
};
}

我们的Vue版本是2.6所以执行的是Vue.mixin({ beforeCreate: vuexInit });会给我们的Vue对象全局混入一个beforeCreate的钩子函数,每一个子组件创建的时候都会执行这个vuexInit函数,我们来看下这个到底是个什么东西

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实例上加上一个$store属性,回想我们使用的时候,是不是都是this.$store.state,就是这个vuexInit初始化的函数,就是通过Vue混入的方式,让我们每一个组件都能够访问到我们的Vuex保存的数据。官方文档上也告诉我们mixin这个函数在开发的时候尽量少用,因为他会影响每一个子组件,比较适合开发插件的时候。

注册完之后,我们就要执行new Vuex.Store()这个构造函数,
我们看下他的源码:

var Store = function Store (options) {
var this$1 = this;
if ( options === void 0 ) options = {};
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}

if ((process.env.NODE_ENV !== 'production')) {
assert(Vue, "must call Vue.use(Vuex) before creating a store instance.");
assert(typeof Promise !== 'undefined', "vuex requires a Promise polyfill in this browser.");
assert(this instanceof Store, "store must be called with the new operator.");
}

var plugins = options.plugins; if ( plugins === void 0 ) plugins = [];
var strict = options.strict; if ( strict === void 0 ) strict = false;

this._committing = false;
this._actions = Object.create(null);
this._actionSubscribers = [];
this._mutations = Object.create(null);
this._wrappedGetters = Object.create(null);
this._modules = new ModuleCollection(options);
this._modulesNamespaceMap = Object.create(null);
this._subscribers = [];
this._watcherVM = new Vue();
this._makeLocalGettersCache = Object.create(null);
var store = this;
var ref = this;
var dispatch = ref.dispatch;
var commit = ref.commit;
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
};
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
};
this.strict = strict;
var state = this._modules.root.state;

installModule(this, state, [], this._modules.root);   //处理我们的配置

resetStoreVM(this, state);    //处理响应式
plugins.forEach(function (plugin) { return plugin(this$1); });

var useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools;
if (useDevtools) {
devtoolPlugin(this);
}
};

这个就是初始化的过程给我们的Vuex对象根据我们配置的参数添加上属性,比较关键就是installModule和resetStoreVM这两个函数。
installModule这个函数会对我们配置的mutations, actions, getters进行处理

function installModule (store, rootState, path, module, hot) {
var isRoot = !path.length;
var namespace = store._modules.getNamespace(path);

// register in namespace map
if (module.namespaced) {
if (store._modulesNamespaceMap[namespace] && (process.env.NODE_ENV !== 'production')) {
console.error(("[vuex] duplicate namespace " + namespace + " for the namespaced module " + (path.join('/'))));
}
store._modulesNamespaceMap[namespace] = module;
}

// set state
if (!isRoot && !hot) {
var parentState = getNestedState(rootState, path.slice(0, -1));
var moduleName = path[path.length - 1];
store._withCommit(function () {
if ((process.env.NODE_ENV !== 'production')) {
if (moduleName in parentState) {
console.warn(
("[vuex] state field \"" + moduleName + "\" was overridden by a module with the same name at \"" + (path.join('.')) + "\"")
);
}
}
Vue.set(parentState, moduleName, module.state);
});
}

var local = module.context = makeLocalContext(store, namespace, path);

module.forEachMutation(function (mutation, key) {
var namespacedType = namespace + key;
registerMutation(store, namespacedType, mutation, local);
});

module.forEachAction(function (action, key) {
var type = action.root ? key : namespace + key;
var handler = action.handler || action;
registerAction(store, type, handler, local);
});

module.forEachGetter(function (getter, key) {
var namespacedType = namespace + key;
registerGetter(store, namespacedType, getter, local);
});

module.forEachChild(function (child, key) {
installModule(store, rootState, path.concat(key), child, hot);
});
}

他首先是拿命名空间的名称,如果我们是有配置namespaced:true的话,这里我们的例子是空,接着主要就是执行下面的四个函数,为我们的Store对象添加属性
1.forEachMutation:将我们的配置的mutations,push到Store对象的_mutations属性中,如果我们的子模块名字取得相同的话,那么都会被推到_mutations,除非我们配置命名空间namespaced:true(形式:命名空间名/mutation名称)。
2.forEachAction:将我们的配置的actions,push到Store对象的_actions属性中,如果我们的子模块名字取得相同的话,那么都会被推到_actions,除非我们配置命名空间namespaced:true(形式:命名空间名/action名称)。
3.forEachGetter:将我们的配置的mutation,添加到Store对象的_wrappedGetters属性中
4.forEachChild:如果我们的Store中配置过modules子模块就会递归调用installModule。将配置添加到Store对象上

这时候我们这个例子,Store对象的_mutations数组有一个mutation回调,_actions也有一个action回调,就是我们配置的,因为我都只写了一个,installModule执行之后, resetStoreVM(this, state)也很关键
这个是干嘛的呢,是将我们的states对象变成响应式
看下源码:

function resetStoreVM (store, state, hot) {
var oldVm = store._vm;
store.getters = {};
store._makeLocalGettersCache = Object.create(null);
var wrappedGetters = store._wrappedGetters;
var computed = {};
forEachValue(wrappedGetters, function (fn, key) {
computed[key] = partial(fn, store);
Object.defineProperty(store.getters, key, {
get: function () { return store._vm[key]; },
enumerable: true // for local getters
});
});
var silent = Vue.config.silent;
Vue.config.silent = true;
//这里这里这里这里这里这里这里这里这里这里这里这里这里这里这里这里
store._vm = new Vue({
data: {
$$state: state
},
computed: computed
});
Vue.config.silent = silent;

// enable strict mode for new vm
if (store.strict) {
enableStrictMode(store);   //严格模式
}

if (oldVm) {
if (hot) {
store._withCommit(function () {
oldVm._data.$$state = null;
});
}
Vue.nextTick(function () { return oldVm.$destroy(); });
}
}

这里Vuex又创建了一个vue对象,data的属性就是我们的state状态值,这样当我们访问我们的state的时候就会被set方法劫持到,再通知我们的Watcher发生变化,这样我们的页面就更新了,如果我们设置了strict:true,也就是严格模式,那么Vuex还会执行enableStrictMode(store)这个函数,来监视我们的state,提醒我们的开发者不能在mutations以外的地方修改state

function enableStrictMode (store) {
store._vm.$watch(function () { return this._data.$$state }, function () {
if ((process.env.NODE_ENV !== 'production')) {
assert(store._committing, "do not mutate vuex store state outside mutation handlers.");
}
}, { deep: true, sync: true });
}

他就是Vue的监视函数,当我们的state被访问的时候,就会进行校验他的入口是不是mutations,如果不是,那么就会报错,但是并不影响之后的渲染。经过这一套流程,我们的Vuex初始化就完成了,Vuex的设计就是单向数据流,修改数据应该要在mutations中修改,这样方便追踪,方便我们管理

之后就是new Vue,在beforeCreate钩子上为我们每一个Vue实例添加上$store属性。这个上面介绍过了,在install插件的时候混入到了全局的Vue中了
最后我们看下store是如何dispatch触发actions,actions如何commit通知mutaions
我在子组件中的一个点击事件中写了这样一段
this.$store.dispatch(“setInfo”,“yuyuyu”)。我们看下他的流程,首先进入dispatch

Store.prototype.dispatch = function dispatch (_type, _payload) {
var this$1 = this;

// check object-style dispatch
var ref = unifyObjectStyle(_type, _payload);
var type = ref.type;
var payload = ref.payload;

var action = { type: type, payload: payload };
var entry = this._actions[type];
if (!entry) {
if ((process.env.NODE_ENV !== 'production')) {
console.error(("[vuex] unknown action type: " + type));
}
return
}

try {
this._actionSubscribers
.slice()
.filter(function (sub) { return sub.before; })
.forEach(function (sub) { return sub.before(action, this$1.state); });
} catch (e) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn("[vuex] error in before action subscribers: ");
console.error(e);
}
}

var result = entry.length > 1
? Promise.all(entry.map(function (handler) { return handler(payload); }))
: entry[0](payload);

return new Promise(function (resolve, reject) {
result.then(function (res) {
try {
this$1._actionSubscribers
.filter(function (sub) { return sub.after; })
.forEach(function (sub) { return sub.after(action, this$1.state); });
} catch (e) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn("[vuex] error in after action subscribers: ");
console.error(e);
}
}
resolve(res);
}, function (error) {
try {
this$1._actionSubscribers
.filter(function (sub) { return sub.error; })
.forEach(function (sub) { return sub.error(action, this$1.state, error); });
} catch (e) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn("[vuex] error in error action subscribers: ");
console.error(e);
}
}
reject(error);
});
})
};

dispatch首先查看我们"setInfo"这个函数名是否存在,存在就执行,但是又有一些不同

//这个entry = this._actions[type]  就是_actions中的相同名字的函数
var result = entry.length > 1
? Promise.all(entry.map(function (handler) { return handler(payload); }))
: entry[0](payload);

他最终返回的是一个promise,如果我们的actions只有一个为什么返回的也是promise,其实在push到Store对象_actions上的时候又被封装了一层,真正执行就是下面的函数,

function wrappedActionHandler (payload) {
//handler就是我们配置的setInfo函数
var res = handler.call(store, {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload);
if (!isPromise(res)) {
res = Promise.resolve(res);
}
if (store._devtoolHook) {
return res.catch(function (err) {
store._devtoolHook.emit('vuex:error', err);
throw err
})
} else {
return res
}
});

handler就是我们配置的setInfo函数
执行完handler, Vuex先看这个函数返回的是不是promise,如果不是那么就是将返回结果变成一个promise,所以我们的actions是可以处理一些异步的请求的。

最后我们看一下commit函数,当我们在actions中通知mutations发生状态的改变

Store.prototype.commit = function commit (_type, _payload, _options) {
var this$1 = this;
var ref = unifyObjectStyle(_type, _payload, _options);
var type = ref.type;
var payload = ref.payload;
var options = ref.options;

var mutation = { type: type, payload: payload };
var entry = this._mutations[type];
if (!entry) {
if ((process.env.NODE_ENV !== 'production')) {
console.error(("[vuex] unknown mutation type: " + type));
}
return
}
//这里这里这里这里这里这里这里这里这里这里这里这里这里这里
this._withCommit(function () {
entry.forEach(function commitIterator (handler) {
handler(payload);
});
});
this._subscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.forEach(function (sub) { return sub(mutation, this$1.state); });

if (
(process.env.NODE_ENV !== 'production') &&
options && options.silent
) {
console.warn(
"[vuex] mutation type: " + type + ". Silent option has been removed. " +
'Use the filter functionality in the vue-devtools'
);
}
};

也是先看_mutations中,是否存在这个type的mutation,如果有最终执行这一段代码,handler就是我们在mutations中自己写的setInfo函数

this._withCommit(function () {
entry.forEach(function commitIterator (handler) {
handler(payload);
});
});

_withCommit 的源码:

Store.prototype._withCommit = function _withCommit (fn) {
var committing = this._committing;
this._committing = true;
fn();
this._committing = committing;
};

这个_committing就是声明入口的,如果我们在其他地方直接修改state的状态,那么就会被提醒,要在mutations中修改state的属性,
执行fn()就是执行我们的setinfo函数,修改我们的状态,触发页面的重新渲染。
这样Vuex的整个流程大概就是这样

其实Vuex给我的感觉就是全局变量,你可以在Vue的任何一个实例中使用他们,只不过规定你只能在mutations中修改他的状态。

还有mapstates, mapActions,mapGetters,mapMutations想要了解的话,还是要我们自己动手去debugger一下,记忆才是最深刻的

这就是我今天的分享!有不对的欢迎指正

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: