今天改vue项目的时候发现一个问题,
devtool这个插件的vuex tab里显示的不是我当前页面的store。
纳尼?!!浑身发抖好吗,我们的项目不是spa,该页面没有引入其他store,也没有用vuex的modules机制,甚至数据都是对的,只是devtool plugin里显示的内容对不上号。
究竟发生了什么?(赶时间的同学可直接跳到结尾^_^)
分析一下现场:
- chrome控制台 vue devtool插件中的store
- 当前页面的root app绑定的store
- 页面B(第三者,元凶)的store
难道是cache了?随后,就在callstack里发现了cache关键字眼:
随后长枪如龙,一顿操作猛如虎:
rm -rf node_modules/
npm install
复制代码
问题依在
cmd shift del Chrome缓存全清
复制代码
问题依在
restart computer...
复制代码
问题依在,(最后像个250..)
种种排查,排除了plugin里vuex内容cache的可能性,
然后开始我想当然地开始怀疑起了vue devtool plugin本身的问题, 难道是尤大的bug?
将github的devtool issues挨个看过去,好像没什么搭边的,
唯一的bug还是chrome的控制台有时切tab会报错,影响到tab页内容显示空白,不过这也跟devtool没关系,
但我还是非常执着而认真的重装了最新的vue devtool...
问题依在
事实证明怀疑官方库实在是愚蠢的行为,程序99.9%的问题都是出于自身。
还是老老实实看我们的项目代码。
仔细看下来发现我们的entry中引入了一个commonEntry,
commonEntry中引入了我们自己的基础组件库
然后基础组件库里的某些组件引用了单独编写的工具类
下面来看下该GirdStore的大致定义:
let GridStore = function GridStore(options){
let state = {
columnConfigs: [],
pageRecords: [],
selectedRecords: [],
};
options.state = Object.assign(state,options.state);
Vuex.Store.call(this, options);
}
GridStore.prototype = Object.create(Vuex.Store.prototype);
export default GridStore
复制代码
可以看到GirdStore继承了Vuex.Store
(或者叫'委托',就像You don't know JavaScript中描述的那样,只是我觉得这个词吧,它有点绕....)
聪明的小伙伴应该发现问题所在了。
new GridStore相当于又new了一个Vuex.Store,导致了我们项目中实际上同时存在了多个vuex的store。
所以数据虽然没问题,但同时存在了一堆垃圾store。
接下来的问题就是这为什么会影响到devtool plugin,导致其vuex tab显示的不是我们想要的内容。
试想devtool plugin能将vuex数据可视化,那势必在vuex的实现体中有所关联。
理所当然的,我们应该尝试去探索new Vuex.Store的时候究竟发生了什么?
下面有个小技巧,不管用的editor是sublime或者vscode等,
常常我们查找函数定义体的时候会跳出来 一堆(有点类似idea里java的interface找实现类...):
这种情况下,如果想快速定位到真正的函数定义体,不妨在chrome控制台中利用step into 功能,
var Store = function Store (options) {
var this$1 = this;
if ( options === void 0 ) options = {};
// Auto install if it is not done yet and `window` has `Vue`.
// To allow users to avoid auto-installation in some cases,
// this code should be placed here. See #731
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;
var state = options.state; if ( state === void 0 ) state = {};
if (typeof state === 'function') {
state = state() || {};
}
// 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();
// 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;
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root);
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state);
// apply plugins
plugins.forEach(function (plugin) { return plugin(this$1); });
if (Vue.config.devtools) {
devtoolPlugin(this);
}
};
复制代码
仔细看,我们注意到最后有一个vue.config.devtools的判断,原来就是这里将vuex关联上了插件。
然后我们继续通过step into进入到devtoolPlugin的实现体,源码如下:
function devtoolPlugin (store) {
if (!devtoolHook) { return }
store._devtoolHook = devtoolHook;
devtoolHook.emit('vuex:init', store);
devtoolHook.on('vuex:travel-to-state', function (targetState) {
store.replaceState(targetState);
});
store.subscribe(function (mutation, state) {
devtoolHook.emit('vuex:mutation', mutation, state);
});
}
复制代码
果不其然, 我们可以看到devtoolHook.emit('vuex:init',store),
通过一个钩子emit出去了一个vuex的init事件,并且载荷了传进来的store。
接着我们继续step into该emit函数,这时候代码是被压缩的,
我们点击控制台左下角的pretty Print(就那个大括号图标),将代码格式化一下,
emit(t) {
var e = "$" + t,
n = r[e];
if (n) {
var i = [].slice.call(arguments, 1);
n = n.slice();
for (var o = 0, u = n.length; o < u; o++)
n[o].apply(this, i)
} else {
var f = [].slice.call(arguments);
this._buffer.push(f)
}
}
复制代码
第一次初始化时调用了off方法,移除了上下文中的init事件,下次再调用devtoolPlugin的时候,r中已无init属性, n拿到的就是空数组,也就不会再次拿传进来的store去重新做初始化操作。
看到这里,我想大伙应该都明白了,
vuex devtool plugin关联vuex的初始化操作可以说是单例的,只会显示第一个new Vuex.Store的store。
呼,终于破案。。。。。。。喝杯水压压惊
-
首先我们自己的基础组件库里有不少都有引用vuex store,
有的是直接引用,有的是import的工具类里引用, 并且调用了new Vuex.Store,造成了同时存在了多个Store, 然后实际上只有当前页面root app自己绑定的store是有用的。 我觉得这肯定是有问题的(不知道大家怎么想) 复制代码
-
其次,我想后面一通断点加源码分析,大家伙肯定想试验一个问题,究竟是不是像我分析的那样,
只有第一次调用new Vuex.Store的store会显示在devtool plugin里。 复制代码
下面大家不妨自行做个试验,
类似如下
new Vuex.Store({
state: {
test: 1
}
})
new Vuex.Store({
state: {
test: 2
}
})
复制代码
然后打开控制台,切到devtool plugin 的vuex tab中,看看state里是不是test: 1。
我先干为敬: