背景:
部分项目目录结构:
page文件夹
a文件夹
models文件夹
comModel.js
b文件夹
models文件夹
comModel.js
问题:
comModel.js同名导致的问题:页面挂载完成,接口无法请求;重新刷新页面,接口正常请求
查询官网
只有 model的注册说明,并无 namespace 属性详细说明,如图:
按照官网的说明,page model 在 production 时按需载入,在 development 时全量载入,page model会向上查找;所以我理解为:将路由切换到当前层级b下的page时,model会按照层级向上查找,绝不会找a层级里面的model文件,即使有重名的,应该会进行覆盖;但是实际操作时却不是这样,从 a 层级切换到 b 层级时,控制台 g_app 对象(一个全局的app对象,里面包含_model,路由等各种全局信息)打印出来的 _models属性(数组,包含全局的model数据)中依旧是a层级的model数据,这个时候我感觉不太对,决定去github上看看
g_app结构如下图:
g_app -> _models 数据结构如下图:
查看 github issues
通过查询 github 上,umijs 的 issues,发现确实有 namespace 重复的相关问题,但得到的大多数答案都是 umijs 不支持同名 namespace ,在 3.x版本之后,会有 namespace 重复 的警告提示,建议修改 namespace 名称。官方文档中提到 namespace 是全局 state中的一个属性,所以我就觉得,既然没有这方面的相关处理(比如:层级隔离,重名覆盖等),那源码中会不会使用 namespace 这个属性做了什么全局的处理?比如:如果namespace相同,就不更新。于是先修改了 namespace 使期全局唯一,保证项目正常运行,接下来去查看源码验证自己的猜想。
阅读源码
刚开始直接从 umijs 里面找_model -> namespace,有零星的查找结果,但都是压缩过的代码,结果不理想,这时感觉查找方向不对,突然想起 umijs 集成了 dva.js, 所以转向从 dva.js 里面查找,果然找到了,里面有大量 namespace相关操作,源码中关于model替换的核心代码如下:
/**
* Replace a model if it exsits, if not, add it to app
* Attention:
* - Only available after dva.start gets called
* - Will not check origin m is strict equal to the new one
* Useful for HMR
* @param createReducer
* @param reducers
* @param unlisteners
* @param onError
* @param m
*/
function replaceModel(createReducer, reducers, unlisteners, onError, m) {
var store = app._store;
var namespace = m.namespace;
var oldModelIdx = (0, utils$2.findIndex)(app._models, function (model) {
return model.namespace === namespace;
});
if (~oldModelIdx) {
// Cancel effects
store.dispatch({
type: "".concat(namespace, "/@@CANCEL_EFFECTS")
}); // Delete reducers
delete store.asyncReducers[namespace];
delete reducers[namespace]; // Unlisten subscrioptions
(0, subscription.unlisten)(unlisteners, namespace); // Delete model from app._models
app._models.splice(oldModelIdx, 1);
} // add new version model to store
app.model(m);
store.dispatch({
type: '@@dva/UPDATE'
});
}
从注释当中可以看到,Will not check origin m is strict equal to the new one 不会严格对比model数据,代码中也只是使用 namespace 做了比较 return model.namespace === namespace,并没有深入比较对象中其他属性;如果namespacec相同就不会覆盖替换,而是保留原有的model。如果刷新页面,那么全局的 state(_models) 就会失去现有数据,按照现有的路由进行更新,所以才导致刷新页面会正常请求的现象。
总结
从发现问题到得出结论,整个过程虽然不是很顺利,但是让自己对这个库又有了深入的了解,也是一种收获。也验证了自己的猜想是对的。