跳至主要內容

20. 实现异步组件


20. 实现异步组件

defineAsyncComponent函数是一个高阶组件,他的返回值是一个包装组件。此包装组件会根据状态来决定渲染的内容,加载成功后渲染组件,在未渲染成功时渲染一个占位符节点

1.基本实现

let asyncComponent = defineAsyncComponent(() => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			resolve({
				render: () => h('div', 'hi erxiao')
			});
		}, 1000);
	});
});
export function defineAsyncComponent(loader) {
	let Comp = null;
	return {
		setup() {
			const loaded = ref(false);
			loader().then((c) => {
				Comp = c;
				loaded.value = true;
			});
			return () => {
				return loaded.value ? h(Comp) : h(Fragment, '');
			};
		}
	};
}

2.异步组件超时处理

let asyncComponent = defineAsyncComponent({
	loader: () => {
		return new Promise((resolve, reject) => {
			setTimeout(() => {
				resolve({
					render() {
						return h('div', 'hi erxiao');
					}
				});
			}, 1000);
		});
	},
	timeout: 2000,
	errorComponent: {
		render: () => h('Text', '超时错误')
	}
});
export function defineAsyncComponent(options) {
	if (typeof options === 'function') {
		options = { loader: options };
	}
	let Comp = null;
	return {
		setup() {
			const { loader } = options;
			const loaded = ref(false);
			const error = ref(false); // 是否超时
			loader()
				.then((c) => {
					Comp = c;
					loaded.value = true;
				})
				.catch((err) => (error.value = err));
			if (options.timeout) {
				setTimeout(() => {
					error.value = true; // 显示错误组件
				}, options.timeout);
			}
			const placeHolder = h(Fragment, '');
			return () => {
				if (loaded.value) {
					return h(Comp);
				} else if (error.value && options.errorComponent) {
					// 超时显示错误组件
					return h(options.errorComponent);
				}
				return placeHolder;
			};
		}
	};
}

3.异步组件 loading 处理

let asyncComponent = defineAsyncComponent({
	loader: () => {
		return new Promise((resolve, reject) => {
			setTimeout(() => {
				resolve({
					render() {
						return h('div', 'hi erxiao');
					}
				});
			}, 3000);
		});
	},
	timeout: 2000,
	errorComponent: {
		render: () => h('Text', '超时错误')
	},
	delay: 1000,
	loadingComponent: {
		render: () => h('h2', 'loading....')
	}
});
// loading逻辑
const loading = ref(false);
let loadingTimer = null;
if (options.delay) {
	loadingTimer = setTimeout(() => {
		loading.value = true;
	}, options.delay);
} else {
	loading.value = true;
}
const error = ref(false);
loader()
	.then((c) => {
		Comp = c;
		loaded.value = true;
	})
	.catch((err) => (error.value = err))
	.finally(() => {
		loading.value = false;
		clearTimeout(loadingTimer); // 加载完毕的时候清理定时器
	});
// ...
return () => {
	if (loaded.value) {
		return h(Comp);
	} else if (error.value && options.errorComponent) {
		return h(options.errorComponent);
	} else if (loading.value && options.loadingComponent) {
		// 显示loading组件
		return h(options.loadingComponent);
	}
	return placeHolder;
};

4.异步组件加载重试处理

let asyncComponent = defineAsyncComponent({
	loader: () => {
		return new Promise((resolve, reject) => {
			setTimeout(() => {
				reject({
					render() {
						return h('div', 'hi erxiao');
					}
				});
			}, 3000);
		});
	},
	timeout: 2000,
	errorComponent: {
		render: () => h('Text', '超时错误')
	},
	delay: 1000,
	loadingComponent: {
		render: () => h('h2', 'loading....')
	},
	onError(retry) {
		console.log('错了');
		retry();
	}
});
function load() {
	return loader().catch((err) => {
		if (options.onError) {
			return new Promise((resolve, reject) => {
				const retry = () => resolve(load());
				const fail = () => reject(err);
				options.onError(retry, fail);
			});
		} else {
			throw err;
		}
	});
}