跳到主要内容

认识

2023年03月20日
柏拉文
越努力,越幸运

一、认识


通常在异步加载组件时, 我们需要考虑以下几个方面:

  1. 如果组件加载失败或者加载超时, 是否要渲染 Error 组件

  2. 组件在加载时,是否要展示占位的内容? 例如渲染一个 Loading 组件

  3. 组件加载的速度可能很快, 也可能很慢, 是否要设置一个延迟展示的 Loading 组件的时间? 如果组件在 200ms 内没有加载成功才展示 Loading 组件, 这样可以避免由组件加载过快所导致的闪烁。

  4. 组件加载失败后,是否需要重试?

因此, Vue.js 3.0 提供了 defineAsyncComponent 函数, 用于加载异步组件,提供了以下能力:

  1. 允许用户指定加载出错时要渲染的组件

  2. 允许用户指定 Loading 组件, 以及展示该组件的延迟时间

  3. 允许用户设置加载组件的超时时长

  4. 组件加载失败时,为用户提供重试的能力

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 {
// 加载中
}
}
}
}
}

超时机制 实现思路如下:

  1. 通过标识变量来标识异步组件的加载是否已经超时, 即 timeout.value

  2. 开始加载组件的同时(loader().then()) , 开启一个定时器进行计时, 当加载超时后, 将 timeout.value 的值设置为 true, 代表已经超时。当组件卸载完成时,需要及时清除定时器。

  3. 如果 loaded 变量为 false 说明没有加载成功并且 timeout.valuetrue , 说明此时已经超时, 如果用户配置了 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 {
// 加载中
}
}
}
}
}

延时LoadingLoading 组件设置一个延时展示的时间. 例如,当超过 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);
});