认识
一、认识
通常在异步加载组件时, 我们需要考虑以下几个方面:
-
如果组件加载失败或者加载超时, 是否要渲染
Error
组件 -
组件在加载时,是否要展示占位的内容? 例如渲染一个
Loading
组件 -
组件加载的速度可能很快, 也可能很慢, 是否要设置一个延迟展示的
Loading
组件的时间? 如果组件在200ms
内没有加载成功才展示Loading
组件, 这样可以避免由组件加载过快所导致的闪烁。 -
组件加载失败后,是否需要重试?
因此, Vue.js 3.0
提供了 defineAsyncComponent
函数, 用于加载异步组件,提供了以下能力:
-
允许用户指定加载出错时要渲染的组件
-
允许用户指定
Loading
组件, 以及展示该组件的延迟时间 -
允许用户设置加载组件的超时时长
-
组件加载失败时,为用户提供重试的能力
defineAsyncComponent
本质上是一个高阶组件, 它的返回值是一个包装组件。
二、细节
2.1 超时机制
function defineAsyncComponent(options){
const { loader } = options;
return {
name: 'AsyncComponentWrapper',
setup(){
const loaded = ref(false);
const timeout = ref(false);
loader().then(c=>{
loaded.value = true;
});
let timer = null;
if(options.timeout){
setTimeout(()=>{
timeout.value = true;
},options.timeout);
}
onUnmounted(()=>{ clearTimeout(timer) });
return ()=>{
if(loaded.value){
// 如果加载成功
}else if(timeout.value){
// 如果加载超时
}else {
// 加载中
}
}
}
}
}
超时机制 实现思路如下:
-
通过标识变量来标识异步组件的加载是否已经超时, 即
timeout.value
-
开始加载组件的同时(
loader().then()
) , 开启一个定时器进行计时, 当加载超时后, 将timeout.value
的值设置为true
, 代表已经超时。当组件卸载完成时,需要及时清除定时器。 -
如果
loaded
变量为false
说明没有加载成功并且timeout.value
为true
, 说明此时已经超时, 如果用户配置了Error
组件, 则渲染Error
组件。
2.2 错误捕获
function defineAsyncComponent(options){
const { loader } = options;
return {
name: 'AsyncComponentWrapper',
setup(){
const loaded = ref(false);
const error = shallowRef(null);
loader().then(c=>{
loaded.value = true;
}).catch((err)=>{
error.value = err;
});
let timer = null;
if(options.timeout){
setTimeout(()=>{
const err = new Error(……);
error.value = err;
},options.timeout);
}
onUnmounted(()=>{ clearTimeout(timer) });
return ()=>{
if(loaded.value){
// 如果加载成功
}else if(error.value && options.errorComponent){
// 如果加载超时
return { type: options.errorComponent, props: { error: error.value }}
}else {
// 加载中
}
}
}
}
}
错误捕获 提供了以下能力: 当错误发生时, 把错误对象作为 Error
组件的 Props
传递过去, 以便用户后续能自行进行更细粒度的处理
2.3 延时Loading
function defineAsyncComponent(options){
const { loader } = options;
return {
name: 'AsyncComponentWrapper',
setup(){
const loaded = ref(false);
const error = shallowRef(null);
const loading = ref(false);
let loadingTimer = null;
if(options.delay){
loadingTimer = setTimeout(()=>{
loading.value = true;
},options.delay);
}else{
loading.value = true;
}
loader().then(c=>{
loaded.value = true;
}).catch((err)=>{
error.value = err;
}).finally(()=>{
loading.value = false;
clearTimeout(loadingTimer);
});
let timer = null;
if(options.timeout){
setTimeout(()=>{
const err = new Error(……);
error.value = err;
},options.timeout);
}
onUnmounted(()=>{ clearTimeout(timer) });
return ()=>{
if(loaded.value){
// 如果加载成功
}else if(error.value && options.errorComponent){
// 如果加载超时
return { type: options.errorComponent, props: { error: error.value }}
}else if(loading.value && options.loadingComponent){
return { type: options.loadingComponent }
}else {
// 加载中
}
}
}
}
}
延时Loading
为 Loading
组件设置一个延时展示的时间. 例如,当超过 200ms
没有完成加载,才展示 Loading
组件, 这样对于在 200ms
内能够完成加载的情况来说就避免了闪烁问题的出现。
2.4 错误重试机制
// 模拟失败请求
function fetch(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('失败接口');
},3000);
});
}
// 记录重试次数
let retries = 0;
function load(onError){
// 请求接口
const p = fetch();
// 捕获错误
return p.catch(err=>{
return new Promise((resolve,reject)=>{
const retry = ()=> {
resolve(load(onError));
retries++;
};
const fail = ()=> reject(err);
onError(retry,fail,retries);
});
});
}
// 调用 load 加载资源
load((retry,fail,retries)=>{
if(retries <= 6){
console.log(`重试 ${retries} 次`);
retry();
}else{
console.log("重试 6 次之后停止,不再继续请求");
fail();
}
}).then(res=>{
// 请求成功
console.log(res);
}).catch((error)=>{
// 请求失败
console.log(error);
});