写在最前
看官们好,我是JetTsang,之前都是在潜水来着,现在偶然做一些内容输出吧。
问题引出
项目技能栈: vue2.6
由于事务场景需求,会在同一个路由组件傍边,有一部分页面的组件需求依据数据类型的不同,显现对应的页面组件。
页面结构的示意图:
而在逐渐的迭代进程傍边,数据类型越来越多,使用之前的同步组件引入时,会导致该路由组件过于臃肿,进而影响到该路由页面的加载速度。就像这样:
后续规划了动态异步组件Component + AsyncComponent
的方案
vue2官方文档-异步组件
异步组件采用了Vue官方的文档示例的工厂函数的方式去编写
方案如下:
当然这儿有好几种变体写法,vue注册的组件,能够是特定的对象,函数,promise等。
然后打包体积果然减少了,成功缩减了该路由页面的体积。
但在后来遇到了问题:这儿异步组件只加载1次,假如失利了就会显现对应的ErrorComponent
,之后再加载也只会加载到失利的组件,不会再次去发恳求。
我预想的成果是,在失利组件那里点击从头加载,或许用户经过路由操作回来后,能从头恳求组件。
看了网上的一些解决方案,说是需求reload
这个页面,难道这样了吗?
遇到问题,要先镇定分析。
问题剖析
首先看下render函数上写的是什么,在vue开发者面板能够找到这个功用
function render() {
var _vm = this,
_c = _vm._self._c;
return _c("div", [
_c("button", {
on: {
click: _vm.handleClick
}
}, [_vm._v(" 点击加载组件 ")]),
_c("hr"),
_c(_vm.asyncCom, {
tag: "Component"
})
], 1);
}
对应的template为:
<template>
<div>
<button @click="handleClick" >
点击加载组件
</button>
<hr/>
// 这儿是要点
<Component :is="asyncCom"/>
</div>
</template>
能够忽略掉我写的最外层的div,以及测试用的button和hr元素,最终最要害的Component
is就被编译成了_c(_vm.asyncCom, { tag: "Component" })
这个_c
便是createElement
,检查一下源码
依据条件,会进入到createComponent
那终究的路径便是这样:
createElement –> _createElement –> createComponent –> resolveAsyncComponent
那么只需求仔细分析resolveAsyncComponent
export function resolveAsyncComponent(
factory: { (...args: any[]): any; [keye: string]: any },
baseCtor: typeof Component
): typeof Component | void {
// 假如有errorComp特点和error为true,则回来错误的组件
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}
// 假如已经resolve状况了,则回来resolved的成果
if (isDef(factory.resolved)) {
return factory.resolved
}
const owner = currentRenderingInstance
// 假如最近烘托的实例里没有在owners里而且owners存在,则放入到owners数组里记录下来
if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
// already pending
factory.owners.push(owner)
}
// 假如在loading,则加载loading组件
if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
return factory.loadingComp
}
//假如有当时烘托实例,而且factory.owners不存在,则敞开加载流程
if (owner && !isDef(factory.owners)) {
// 这儿省略了。。。。
// return in case resolved synchronously
return factory.loading ? factory.loadingComp : factory.resolved
}
}
这儿的factory
便是咱们传入的_vm.asyncCom
,会发现它给_vm.asyncCom
放了一些特点,依据这些特点来回来对应的组件。那么咱们打印一下_vm.asyncCom
看看
不出所料!
解决方案
已然到这儿,问题解决方案就很简单了
方案一(不引荐):
能够给asyncCom
从头设置好error 和owners,让源码里的判断失效,能继续走到加载组件的逻辑里
// 在失效的组件errorComp里,设置对应的内容,同时强制刷新组件
this.$parent.asyncCom.error = false
this.$parent.asyncCom.owners = null
this.$forceUpdate()
能够看到的确收效了 但这有明显的问题,这太hack了,假如源码里的变量名后续改了,又会失效,换言之,耦合程度太高。
方案二(引荐):
这儿的问题在于源码给factory
放了特点,那咱们只要每次都回来新的factory
不就能够。
这儿的方法许多,比方将comMap改造成工厂函数回来。或许爽性把ComMapFactory写在vue的data里边,或许函数里边等等。
// 改造成工厂函数
const ComMapFactory = () =>( {
// 这儿留意⚠️:import 一定要静态写出来,由于webpack是静态编译的
A: ()=> AsyncComponent(import('xxx')),
B: ()=> AsyncComponent(import('xxx')),
C: ()=> AsyncComponent(import('xxx')),
D: ()=> AsyncComponent(import('xxx')),
})
最终小疑问: 为什么成功获取到组件之后,再次点击就不会再恳求一次组件呢?
2个原因:
- 这其实是webpack的Runtime里边有对应模块的缓存,当已经获取到对应的组件之后,再次
import('xxx')
会直接从缓存里边取值 - 刚刚的
resolveAsyncComponent
也提到了,一旦resolved,就会直接回来resolved