Vue3 18.组件07 异步组件
基本用法
在大型项目中,经常需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue提供了defineAsyncComponent
方法来实现此功能:
import { defineAsyncComponent } from 'vue' |
defineAsyncComponent
方法接收一个返回Promise的加载函数。这个Promise的resolve
回调方法应该在从服务器获得组件定义时调用。也可以调用reject(reason)
表明加载失败。
ES模块的动态导入也会返回一个Promise,所以多数情况下会将其与defineAsyncComponent
搭配使用。类似vite和webpack这样的构建工具也支持这样的语法,因此可以用它来导入Vue单文件组件:
import { defineAsyncComponent } from 'vue' |
最后得到的AsyncComp
是一个外层包装过的组件,仅在页面需要它渲染时才会调用加载内部实际组件的函数。它会将接收到的props和插槽传给内部组件,所以可以使用异步的包装组件无缝替换原始组件,同时实现延迟加载。
与普通组件一样,异步组件可以使用app.component()
全局注册:
app.component('MyComponent', defineAsyncComponent(() => import('./components/MyComponent.vue'))) |
也可以直接在父组件中直接定义它们:
<script setup> |
加载与错误状态
异步操作不可避免会涉及到加载和错误状态,因此defineAsyncComponent()
也支持在高级选项中处理这些状态:
const AsyncComp = defineAsyncComponent({ |
如果提供了一个加载组件,它将在内部组件加载时先行显示。在加载组件显示之前有一个默认的200ms延迟–这是因为在网络状况比较好的时候,加载完成的很快,加载组件和最终组件之间的替换太快可能产生闪烁,反而影响感受。
如果提供了一个报错组件,则它会在加载器函数返回的Promise报错时被渲染。还可以指定一个超时时间,在请求耗时超过指定时间时也会渲染报错组件。
搭配Suspense组件使用
<Suspense>
是一个内置组件,用来在组件树中协调对异步依赖的处理。使用它可以在组件树上层等待下层的多个嵌套异步依赖项解析完成,并可以在等待时渲染一个加载状态。
异步依赖
假设现在有这样一种组件层级结构:
<Suspense> |
在这个组件树中有多个嵌套组件,要渲染它们,首先得解析一些异步资源。如果没有<Suspense>
,则它们每个都需要处理自己的加载、报错和完成状态,在最坏的情况下,可能会在页面上看到三个旋转的加载态,在不同的时间显示出内容。
有了<Suspense>
组件后,就可以在等待整个多层级组件树中的各个异步依赖获取结果时,在顶层展示出加载中或加载失败的状态。
<Suspense>
可以等待的异步依赖有两种:
- 带有异步
setup()
钩子的组件。也包含了使用<script setup>
时有顶层await
表达式的组件。 - 异步组件。
async setup()
组合式API中组件的setup()
钩子可以是异步的:
export default { |
如果使用的是<script setup>
,那么顶层await
表达式会自动让该组件成为个异步依赖:
<script setup> |
异步组件
异步组件默认就是suspensible
的,这意味着如果组件关系链上有一个<Suspense>
,那么这个异步组件就会被当做是这个<Suspense>
的一个异步依赖。在这种情况下,加载状态是由<Suspense>
控制,而该组件自己的加载、报错、延时和超时等选项都将被忽略。
异步组件也可以通过在选项中指定suspensible: false
表明不用Suspense
控制,并让组件始终自己控制其加载状态。
加载中状态
<Suspense>
组件有两个插槽:#default
和#fallback
。这两个插槽都只允许一个直接子节点。在可能的时候都将显示默认插槽中的节点。否则将显示后备插槽中的节点。
<Suspense> |
在初始渲染时,<Suspense>
将在内存中渲染其默认的插槽内容。如果在这个过程中遇到任何异步依赖,则会进入挂起
状态。在挂起状态期间,展示的是后备内容。当所有遇到的异步依赖都完成后,<Suspense>
会进入完成状态,并将展示出默认插槽的内容。
如果在初次渲染时没有遇到异步依赖,<Suspense>
会直接进入完成状态。
进入完成状态后,只有当默认插槽的根节点被替换时,<Suspense>
才会回到挂起状态。组件树中新的更深层次的异步依赖不会造成<Suspense>
回退到挂起状态。
发生回退时,后备内容不会立即展示出来。相反<Suspense>
在等待新内容和异步依赖完成时,会展示之前#default
插槽的内容。这个行为可以通过一个timeout
prop进行配置: 在等待渲染新内容耗时超过timeout
之后,<Suspense>
将会切换为展示后备内容。若timeout
值为0将导致在替换默认内容时立即显示后备内容。
事件
<Suspense>
组件会触发三个事件:pending
、resolve
和fallback
。pending
事件是在进入挂起状态时触发。resolve
事件是在default
插槽完成获取新内容时触发。fallback
事件则是在fallback
插槽的内容显示时触发。
例如,可以使用这些事件在加载新组件时在之前的DOM最上层显示一个加载指示器。
错误处理
<Suspense>
组件自身还不提供错误处理,可以使用errorCaptured
选项或者onErrorCaptured()
钩子,在使用到<Suspense>
的父组件中捕获和处理异步错误。
和其他组件结合
通常在使用过程中,会将<Suspense>
和<Transition>
、<KeepAlive>
等组件结合。要保证这些组件都能正常工作,嵌套的顺序非常重要。
另外,这些组件都通常与VueRouter的<RouterView>
组件结合使用。
下面的示例展示了如何嵌套这些组件,使它们都能按照预期的方式运行。
<RouterView v-slot="{ Component }"> |
VueRouter使用动态导入对懒加载组件进行了内置支持。这些与异步组件不同,目前他们不会触发<Suspense>
。但是它们仍然可以有异步组件作为后代,这些组件可以照常触发<Suspense>
。
嵌套使用
当有多个类似与下方的异步组件时:
<Suspense> |
<Suspense>
创建了一个边界,它将如预期的那样解析树下的所有异步组件。然而当更改DynamicAsyncOuter
时,<Suspense>
会正确地等待它,但当更改DynamicAsyncInner
时,嵌套的DynamicAsyncInner
会呈现为一个空节点,直到它被解析为止(而不是之前的节点或者回退插槽)。
为了解决这个问题,可以使用嵌套的方法来处理嵌套组件:
<Suspense> |
如果不设置suspensible
属性,内部的<Suspense>
将被父级<Suspense>
视为同步组件。这意味着它将有自己的回退插槽,如果两个Dynamic
组件同时被修改,则当子<Suspense>
加载其自己的依赖关系树时,可能会出现空节点和多个修补周期,这可能不是理想情况。设置后,所有异步依赖项处理都会交给父级<Suspense>
(包括发出的事件),而内部<Suspense>
仅充当依赖项解析和修补的另一个边界。