超时与Error组件

在弱网环境下,加载一个组件可能需要很长时间,因此,要为用户提供指定超时时长的能力,当加载组件的时间超过了指定时长后,会触发超时错误。这时如果用户配置了Error组件,则会渲染该组件

首先设计用户接口,为了让用户能够指定超时时长,defineAsyncComponent函数需要接受一个配置对象作为参数:

const AsyncComp = defineAsyncComponent({
	loader: ()=>import('CompA.vue'),
	timeout: 2000, // 超时时长,其单位是ms
	errorComponent: MyErrorComp // 指定出错时要渲染的组件
})

设计好用户接口后,就可以给出具体实现了,如下面代码所示:

function defineAsyncComponent(options){
	// options可以是配置项,也可以是加载器
	if(typeof options = 'function'){
		// 如果options是加载器,则将其格式化为配置项形式
		options = {
			loader: options
		}
	}
	const {loader} = options
	
	let InnerComp = null
	
	return {
		name: 'AsyncComponentWrapper',
		setup(){
			const loaded = ref(false)
			// 代表是否超时,默认为false,即没有超时
			const timeout = ref(false)
			
			loader().then(c => {
				InnerComp = c;
				loader.value = true
			})
			
			let timer = null
			if(option.timeout){
				// 如果制定了超时时长,则开启一个定时器计时
				timer = setTimeout(()=>{
					// 超时后将 timeout 设置为true
					timeout.value = true
				}, options.timeout)
			}
			// 包装组件被卸载时清除定时器
			onUnmounted(()=>{
				clearTimeout(timer)
			})
			// 占位内容
			const placeholder = {type:Text, children: ''}
			return ()=>{
				if(loaded.value){
					return {type: InnerComp }
				}else if(timeout.value){
					// 如果加载超时,并且用户指定了Error组件,则渲染该组件
					return options.errorComponent ? {type: options.errorComponent}: placeholder
				}
				return placeholder
			}
		}
	}	
}

要注意的点:

  1. 需要一个标志变量来标识是否已经超时
  2. 当包装组件被卸载时,需要清除定时器
  3. 根据loaded变量的值以及timeout变量的值来决定具体的渲染内容

这样就实现了对加载超时的兼容,以及对Error组件的支持。除此之外,还要为用户提供以下能力:

  1. 当错误发生时,把错误对象作为Error组件的props传递过去,以便用户后续能自行进行更细粒度的处理
  2. 除了超时之外,还有有能力处理其他原因导致的加载错误,例如网络失败等

对代码做如下调整:

function defineAsyncComponent(options){
	if(typeof options = 'function'){
		options = {
			loader: options
		}
	}
	const {loader} = options
	
	let InnerComp = null
	
	return {
		name: 'AsyncComponentWrapper',
		setup(){
			const loaded = ref(false)
			// 定义error,当错误发生时,用来存储错误对象
			const error = shallowRef(null)
			
			loader().then(c => {
				InnerComp = c;
				loader.value = true
			})// 添加catch语句来捕获加载过程中的错误
			.catch((err)=>{error.value = err})
			
			let timer = null
			if(option.timeout){
				// 如果制定了超时时长,则开启一个定时器计时
				timer = setTimeout(()=>{
					// 超时后创建一个错误对象,并复制给error.value
					const err = new Error(`Async component timed out after ${options.timeout}ms.`)
					error.value = err
				}, options.timeout)
			}
			
			// 包装组件被卸载时清除定时器
			onUnmounted(()=>{
				clearTimeout(timer)
			})
			// 占位内容
			const placeholder = {type:Text, children: ''}
			return ()=>{
				if(loaded.value){
					return {type: InnerComp }
				}else if(error.value && options.errorComponent){
					// 只有当错误存在且用户配置了errorComponent时才展示Error组件,同时将error作为props传递
					return {type:options.errorComponent, props:{error:error.value}}
				}
				return placeholder
			}
		}
	}	
}

在组件渲染时,只要error.value的值存在,且用户配置了errorComponent组件,就直接渲染errorComponent组件并将error.value的值作为该组件的props传递,这样用户就可以在自己的Error组件上,通过定义名为error的props来接收错误对象,从而实现细粒度的控制