Vue3 异步组件(懒加载组件)
Vue3 异步组件(懒加载组件)
- 1. 基本用法
- 1.1 基本语法(defineAsyncComponent )
- 1.2 使用示例
- 1.2.1 非异步组件(正常 import,会导致初始加载时间变长)
- 1.2.2 异步组件(使用 defineAsyncComponent 异步加载组件,减少初始加载时间 )
- 2. 使用细节
- 2.1 注册方式
- 2.1.1 全局注册
- 2.1.2 局部注册(通常配合 is 和 v-if 进行使用)
- 2.2 加载与错误状态(delay 和 timeout 无效)
1. 基本用法
异步组件,就是需要时才异步加载的组件。
1.1 基本语法(defineAsyncComponent )
在 Vue3 中,我们可以通过 defineAsyncComponent 函数来实现异步加载组件:
import { defineAsyncComponent } from 'vue'const AsyncComp = defineAsyncComponent(() => {return new Promise((resolve, reject) => {// ...从服务器获取组件resolve(/* 获取到的组件 */)})
})
// ... 像使用其他一般组件一样使用 `AsyncComp`
ES 模块动态导入也会返回一个 Promise,所以多数情况下我们会将它和 defineAsyncComponent 搭配使用:
import { defineAsyncComponent } from 'vue'const AsyncComp = defineAsyncComponent(() =>import('./components/MyComponent.vue')
)
最后得到的
AsyncComp是一个外层包装过的组件,仅在页面需要它渲染时才会调用加载内部实际组件的函数。它会将接收到的props和插槽传给内部组件,所以你可以使用这个异步的包装组件无缝地替换原始组件,同时实现延迟加载。
1.2 使用示例
1.2.1 非异步组件(正常 import,会导致初始加载时间变长)
在我们的项目中,通常我们会使用 import 直接导入需要的组件。
父组件 App.vue:
<template><div id="app"><button @click="currentComponent = A">访问A组件</button><button @click="currentComponent = B">访问B组件</button><component :is="currentComponent" v-if="currentComponent"></component></div>
</template><script setup>
import { shallowRef } from 'vue'
import A from '@/components/A.vue'
import B from '@/components/B.vue'
const currentComponent = shallowRef(null)
</script><style lang="scss" scoped>
button {margin-right: 10px;
}
</style>
这样在应用启动时就会立即加载所有被导入的组件,从而导致初始的加载时间变长,特别是当组件特别多的时候。
子组件 A.vue:
<template><div>这是A组件</div>
</template><script setup></script><style lang="scss" scoped></style>
子组件 B.vue:
<template><div>这是B组件</div>
</template><script setup></script><style lang="scss" scoped></style>
1.2.2 异步组件(使用 defineAsyncComponent 异步加载组件,减少初始加载时间 )
此时我们就可以使用异步组件来进行优化:
<template><div id="app"><button @click="loadComponent('A')">访问A组件</button><button @click="loadComponent('B')">访问B组件</button><component :is="currentComponent" v-if="currentComponent"></component></div>
</template><script setup>
import { shallowRef, defineAsyncComponent } from 'vue'
const currentComponent = shallowRef(null)const loadComponent = (name) => {currentComponent.value = defineAsyncComponent(() => import(`./components/${name}.vue`))
}
</script><style lang="scss" scoped>
button {margin-right: 10px;
}
</style>

经过改动后,现在只有点击了按钮,才会 import 对应的组件,从而减少了初始加载的时间,实现了懒加载的特性。
2. 使用细节
2.1 注册方式
2.1.1 全局注册
与普通组件一样,异步组件可以使用 app.component() 全局注册。示例代码:
app.component('MyComponent', defineAsyncComponent(() =>import('./components/MyComponent.vue')
))

完整示例如下:
main.js:
import { createApp, defineAsyncComponent} from 'vue'
import App from './App.vue'
import router from './router'const app = createApp(App)app.component('A', defineAsyncComponent(() =>import('@/components/A.vue')
))app.component('B', defineAsyncComponent(() =>import('@/components/B.vue')
))app.use(router)app.mount('#app')
App.vue:
<template><div id="app"><button @click="loadComponent('A')">访问A组件</button><button @click="loadComponent('B')">访问B组件</button><component :is="currentComponent" v-if="currentComponent"></component></div>
</template><script setup>
import { shallowRef } from 'vue'
const currentComponent = shallowRef(null)const loadComponent = (name) => {currentComponent.value = name
}
</script><style lang="scss" scoped>
button {margin-right: 10px;
}
</style>
2.1.2 局部注册(通常配合 is 和 v-if 进行使用)
也可以直接在父组件中直接定义它们:
<template><div id="app"><C /></div>
</template><script setup>
import { defineAsyncComponent } from 'vue'const C = defineAsyncComponent(() =>import('@/components/C.vue')
)
</script><style lang="scss" scoped>
button {margin-right: 10px;
}
</style>
不过,我们通常不会直接在组件中使用 <C /> 进行使用,因为异步组件的目的,就是为了需要时加载,所以通常会配合
<component :is="currentComponent" v-if="currentComponent">
这样的语句进行使用,可参考 1.2.2 异步组件 的使用示例。
2.2 加载与错误状态(delay 和 timeout 无效)
defineAsyncComponent() 也支持在高级选项中处理加载和错误状态:
const AsyncComp = defineAsyncComponent({// 加载函数loader: () => import('./Foo.vue'),// 加载异步组件时使用的组件loadingComponent: LoadingComponent,// 展示加载组件前的延迟时间,默认为 200msdelay: 200,// 加载失败后展示的组件errorComponent: ErrorComponent,// 如果提供了一个 timeout 时间限制,并超时了// 也会显示这里配置的报错组件,默认值是:Infinitytimeout: 3000
})
下面,让我们来看一个完整示例。
App.vue:
<template><div id="app"><button @click="loadComponent('A')">访问A组件</button><button @click="loadComponent('D')">访问D组件</button><component :is="currentComponent" v-if="currentComponent"></component></div>
</template><script setup>
import { shallowRef, defineAsyncComponent } from 'vue'
import LoadingComponent from '@/components/LoadingComponent.vue'
import ErrorComponent from '@/components/ErrorComponent.vue'
const currentComponent = shallowRef(null)const loadComponent = (name) => {currentComponent.value = defineAsyncComponent({// 加载函数loader: () => import(`@/components/${name}.vue`),// 加载异步组件时使用的组件loadingComponent: LoadingComponent,// 展示加载组件前的延迟时间,默认为 200msdelay: 3000,// 加载失败后展示的组件errorComponent: ErrorComponent,// 如果提供了一个 timeout 时间限制,并超时了// 也会显示这里配置的报错组件,默认值是:Infinitytimeout: 3000})
}</script><style lang="scss" scoped>
button {margin-right: 10px;
}
</style>
A.vue:
<template><div>这是A组件</div>
</template><script setup></script><style lang="scss" scoped></style>
LoadingComponent.vue:
<template><div>假装我在loading...</div>
</template><script setup></script><style lang="scss" scoped></style>
ErrorComponent.vue:
<template><div>这是加载失败后展示的组件</div>
</template><script setup></script><style lang="scss" scoped></style>
D.vue 未创建。
按理说,如果我点击 “访问A组件” 按钮,会有 3s 中的时间显示加载组件,然后在切换到A组件。但是实际上,却马上切换到了A组件。

按理说,点击D组件,应该也会经历 3s 的延迟后,再显示错误组件。但是实际上却马上显示了报错组件。

这可能需要搭配 <Suspense> 组件才会生效,让我们届时在进行进一步的学习。
上一章 《Vue3 重构待办事项(主要练习组件化)》
