vuex源码解读&简易实现
2020-02-06 本文已影响0人
唐井儿_
源码解读
开始之前,先贴个大纲:首先,我们从使用方法入手,一步步来看
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
msg: ''
},
getters: {
msgPlus: state => state.msg + ' plus'
},
mutations: {
setMsg (store, data) {
store.msg = data;
}
},
actions: {
asyncChangeMsg ({commit}, data) {
setTimeout(() => {
commit('setMsg', data);
}, 2000)
}
}
})
// main.js
import Vue from 'vue';
import App from 'App.vue';
import store from './store';
new Vue({
render(h)=> h(App),
store,
}).$mount('#app')
// App.vue
<template>
<div>
msg: {{$store.state.msg}} <br />
msgPlus: {{$store.getters.msgPlus}}
</div>
</template>
<script>
export default {
created () {
this.$store.dispatch('asyncChangeMsg', 22)
}
}
</script>
接下来
- 从store.js文件入手。Vue.use(Vuex): 据官网介绍,此方法用来安装插件,插件必须提供install方法,且会将Vue作为参数传入;
- 于是,查看导出vuex的地方,找到./node_modules/vuex/dist/vuex.esm.js,就是源码部分了。注:后缀esm(EcmaScript Module)表示es6模块版;
- 此文件底部,查看导出部分,确存在install方法
var index_esm = {
Store: Store,
install: install,
version: '3.1.2',
mapState: mapState,
mapMutations: mapMutations,
mapGetters: mapGetters,
mapActions: mapActions,
createNamespacedHelpers: createNamespacedHelpers
};
export default index_esm;
- 查看install实现,传入的参数是真正的Vue
var Vue; // bind on 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);
}
- 上述代码段先是防止重复调用Vue.use方法,随后调用applyMixin方法
function applyMixin (Vue) {
var version = Number(Vue.version.split('.')[0]);
if (version >= 2) {
// 在每个Vue实例的beforeCreated生命周期时,执行vuexInit方法
Vue.mixin({ beforeCreate: vuexInit });
} else {
// ...
}
}
- 上述代码段,根据不同vue版本分别处理,这里暂且只看2.x及以上版本。
Vue.mixin方法:据官网介绍,全局混入,影响注册后创建的每个Vue实例。此处作用是,在每个Vue实例的beforeCreated生命周期时,执行vuexInit方法。
下边看vuexInit方法做了什么?
function vuexInit () {
// this表示该Vue实例,$options是实例初始化选项
var options = this.$options;
// store injection
// 从options中取store,若存在,则赋值到this.$store上;否则,从父级取得$store进行赋值
// this.$options.parent与this.$parent等价
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;
}
}
- 上述代码段,从main.js入口实例Vue时,初始化传入的store开始,一级级传到子组件,于是每个组件上都有了$store属性。
于是,我们可以在每个组件上,通过this.$store进行操作,例如App.js中的this.$store.state.msg/this.$store.dispatch等
了解了Vue.use(Vuex)之后,再次回到store.js文件,接下来看实例化Store对象过程中发生了什么?注意一点:
// 成员访问的优先级高于new,于是先执行Vuex.Store,而后执行实例化
new Vuex.Store({})
- 首先贴出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;
// store internal state
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);
// bind commit and dispatch to self
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)
};
// strict mode
this.strict = strict;
var state = this._modules.root.state;
// 模块相关
installModule(this, state, [], this._modules.root);
resetStoreVM(this, state);
// apply plugins
plugins.forEach(function (plugin) { return plugin(this$1); });
var useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools;
if (useDevtools) {
devtoolPlugin(this);
}
};
- installModule方法为模块相关,暂且不看,先来看看比较重要的resetStoreVM方法:
function resetStoreVM (store, state, hot) {
var oldVm = store._vm;
// bind store public getters
store.getters = {};
// reset local getters cache
store._makeLocalGettersCache = Object.create(null);
// _wrappedGetters会收集所有getters方法 -- mynote
var wrappedGetters = store._wrappedGetters;
// 将getters的方法包装一层后,收集到computed对象中 -- mynote
// 使用Object.defineProperty注册store.getters,使得每次取值时,从store._vm中取。原因是下方进行new Vue传入computed后,computed的值会放到实例上边 -- mynote
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;
// 将state值 和 getters值分别传入一个new Vue中初始化成data和computed,使其具有响应式特性 -- mynote
store._vm = new Vue({
data: {
$$state: state
},
computed: computed
});
// ...
}
- 参考mynote部分,总的来说,此方法的作用是,借用Vue,使得state值和getters值具有响应式特征
再来看看,commit是如何触发mutations的?
Store.prototype.commit = function commit (_type, _payload, _options) {
var this$1 = this;
// check object-style commit
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
}
// 批量触发mutation
this._withCommit(function () {
entry.forEach(function commitIterator (handler) {
handler(payload);
});
});
// 通知订阅者
this._subscribers.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'
);
}
};
简易实现
// src下,新建vuex.js文件,之前引入vuex的地方改为引用此文件,其他地方无需修改
let Vue;
class Store {
constructor (options) {
this.vm = new Vue({
data: {state: options.state}
})
this.state = this.vm.state;
this.mutations = options.mutations;
this.actions = options.actions;
}
commit (eventName, data) {
this.mutations[eventName](this.state, data)
}
dispatch (eventName, data) {
this.actions[eventName](this, data)
}
}
const install = (_Vue) => {
Vue = _Vue;
Vue.mixin({
beforeCreate () {
if (this.$options && this.$options.store) {
this.$store = this.$options.store;
} else {
this.$store = this.$parent && this.$parent.$store;
}
}
})
}
export default {
Store,
install
}
完整项目可参考。
趁着空闲,记录下学习的知识,有待进一步完善...