异步组件利用了动态import
,下面链接指向对应的文档。
与静态导入不同,动态导入只在需要时进行。并返回一个满足模块名称空间对象的Promise
:一个包含模块中所有导出内容
的对象。例如将下面同步渲染改成异步渲染.只不过是改了一种导入方式,在.then之后执行操作
js
import App from './App'
createApp(App).mount(#app)
js
import('./App').then((App)=>{
createApp(App).mount('#app')
})
这么来说用户也可以自己封装异步组件,但要实现一个完善的异步组件并不轻松。
看看官方的实现defineAsyncComponent
defineAsyncComponent
方法接收一个返回 Promise 的加载函数,得到的是一个封装之后的组件,props 和插槽还是能正常传给内部组件
先来实现一个最简单的defineAsyncComponent
可以看出它返回了一个高阶组件,在它的setup函数中设置一个标记loaded
,加载成功再设为true,这样根据这个标记判断返回组件还是占位组件。
js
// defineAsyncComponent 函数用于定义一个异步组件,接收一个异步组件加载器作为参数
function defineAsyncComponent(loader) {
// 一个变量,用来存储异步加载的组件
let InnerComp = null
// 返回一个包装组件
return {
name: 'AsyncComponentWrapper',
setup() {
// 异步组件是否加载成功
const loaded = ref(false)
// 执行加载器函数,返回一个 Promise 实例
// 加载成功后,将加载成功的组件赋值给 InnerComp,并将 loaded 标记为 true,代表加载成功
loader().then(c => {
InnerComp = c
loaded.value = true
})
return () => {
// 如果异步组件加载成功,则渲染该组件,否则渲染一个占位内容
return loaded.value ? { type: InnerComp } : { type: Text, children: '' }
}
}
}
}
还应该提供超时服务,即超过了一定时间显示错误组件
- 通过官方文档可知
defineAsyncComponent
可以接受一个函数,或者一个对象(包含函数,超时时间,用来替换的错误组件等),这就需要对参数进行格式化 - 进入setup函数时创建一个定时器,定时时间即传入的超时时间,设置初始
超时标志
为flase,定时器结束设为true.并在卸载时销定时器
,避免内存泄漏。 - 最后根据这个超时标识渲染内容,如果没有传错误组件,这里是渲染
placeholder
,即空白文本 - 另外,
.then中的内容是一个微任务
,同步任务结束之后才会进行,此时我们的定时器已经开始。
js
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
loaded.value = true
})
let timer = null
if (options.timeout) {
// 如果指定了超时时长,则开启一个定时器计时
timer = setTimeout(() => {
// 超时后将 timeout 设置为 true
timeout.value = true
}, options.timeout)
}
// 包装组件被卸载时清除定时器
onUmounted(() => clearTimeout(timer))
// 占位内容
const placeholder = { type: Text, children: '' }
return () => {
if (loaded.value) {
// 如果组件异步加载成功,则渲染被加载的组件
return { type: InnerComp }
} else if (timeout.value) {
// 如果加载超时,并且用户指定了 Error 组件,则渲染该组件
return options.errorComponent
? { type: ions.errorComponent }
: placeholder
}
return placeholder
}
}
}
}
现在只解决了超时的错误,但是其他原因也可能导致错误。而且还想知道发生错误的原因是什么
- 要想知道发生了其他错误可以在
catch
中捕获 - 如果传入了错误组件。想知道错误原因可以将错误信息作为
props
传递给错误组件,便于用户分析问题
js
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
loaded.value = true
})
// 添加 catch 语句来捕获加载过程中的错误
.catch(err => (error.value = err))
let timer = null
if (options.timeout) {
timer = setTimeout(() => {
// 超时后创建一个错误对象,并复制给 error.value
const err = new Error(
`Async component timed out after${options.timeout}ms.`
)
error.value = err
}, options.timeout)
}
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 } }
} else {
return placeholder
}
}
}
}
}
异步加载的组件可能很慢,这个时候就需要Loading组件
,但是如果异步组件的加载速度很快,Loading组件将被快速的挂载再卸载,这会造成闪屏
。所以应该设置一段时间之后如果异步组件还没有加载好再挂载Loading组件。
- 首先还是应该设置一个
标识
,初始值为false,代表没有进入加载中,即Loading组件不应该存在。还需要设置一个定时器
,如果定时器结束设置标识为true,渲染Loading组件。 - 当异步组件加载完成,定时器也应该在promise的
finally
中清除 - 如果参数中没有传递超时时间,不需要定时器,直接将标识直接设为true
js
function defineAsyncComponent(options) {
if (typeof options === 'function') {
options = {
loader: options
}
}
const { loader } = options
let InnerComp = null
return {
name: 'AsyncComponentWrapper',
setup() {
const loaded = ref(false)
const error = shallowRef(null)
// 一个标志,代表是否正在加载,默认为 false
const loading = ref(false)
let loadingTimer = null
// 如果配置项中存在 delay,则开启一个定时器计时,当延迟到时后将loading.value 设置为 true
if (options.delay) {
loadingTimer = setTimeout(() => {
loading.value = true
}, options.delay)
} else {
// 如果配置项中没有 delay,则直接标记为加载中
loading.value = true
}
loader()
.then(c => {
InnerComp = c
loaded.value = true
})
.catch(err => (error.value = err))
.finally(() => {
loading.value = false
// 加载完毕后,无论成功与否都要清除延迟定时器
clearTimeout(loadingTimer)
})
let timer = null
if (options.timeout) {
timer = setTimeout(() => {
const err = new Error(
`Async component timed out after${options.timeout}ms.`
)
error.value = err
}, options.timeout)
}
const placeholder = { type: Text, children: '' }
return () => {
if (loaded.value) {
return { type: InnerComp }
} else if (error.value && options.errorComponent) {
return { type: options.errorComponent, props: { error: error.value } }
} else if (loading.value && options.loadingComponent) {
// 如果异步组件正在加载,并且用户指定了 Loading 组件,则渲染Loading 组件
return { type: options.loadingComponent }
} else {
return placeholder
}
}
}
}
}