defineAsyncComponent 用来定义异步组件。

2025-11-23 16:04:26

defineAsyncComponent 用来定义异步组件。

官方链接 👀

不会在初始化的时候立即加载, 而是在渲染到页面时, 进行异步加载。

这样的话就可以将组件拆分为独立的代码块,减少首屏需要加载的资源

使用方式

  1. 最简单的方式是直接传入一个异步组件加载函数:
const AsyncComp = defineAsyncComponent(() => import('./Comp.vue'))
  1. 也可以传入一个配置对象,在加载的同时,指定加载中和加载失败时显示的组件:
const AsyncComp = defineAsyncComponent({
  loader: () => import('./Comp.vue'),
  loadingComponent: LoadingComponent,
  errorComponent: ErrorComponent,
  timeout: 3000
})

实现

1. 基础结构

defineAsyncComponent 的类型定义:

function defineAsyncComponent(
  source: AsyncComponentLoader | AsyncComponentOptions
): Component

type AsyncComponentLoader = () => Promise<Component>

interface AsyncComponentOptions {
  loader: AsyncComponentLoader
  loadingComponent?: Component
  errorComponent?: Component
  delay?: number
  timeout?: number
  suspensible?: boolean
  onError?: (
    error: Error,
    retry: () => void,
    fail: () => void,
    attempts: number
  ) => any
}

defineAsyncComponent 既可以接收加载函数,也可以接收配置对象。

为了统一处理,可以先把函数形式转换成配置对象:

import { isFunction } from '@vue/shared'

export function defineAsyncComponent(options) {
  if (isFunction(options)) {
    options = {
      loader: options
    }
  }

  const { loader } = options
  const component = ref(() => h('span', null, ''))

  return {
    setup() {
      loader().then((comp) => {
        if (comp && comp[Symbol.toStringTag] === 'Module') {
          comp = comp.default
        }
        component.value = comp
      })

      return () => h(component.value)
    }
  }
}
  • 通过响应式变量 component 保存当前正在渲染的组件
  • 初始化时先渲染一个空的 span, 在加载完成后, 将异步组件保存到 component 上
  • 对于使用 import() 动态导入的组件, 返回值是模块对象, 取对应的 default

2. 支持 loadingComponent 和 errorComponent

当前实现只在加载完成后渲染组件, 加载过程中和加载失败时没有对应 UI

import { isFunction } from '@vue/shared'

export function defineAsyncComponent(options) {
  if (isFunction(options)) {
    options = {
      loader: options
    }
  }

  const defaultComponent = () => h('span', null, '')
  const {
    loader,
    loadingComponent = defaultComponent,
    errorComponent = defaultComponent
  } = options

  const component = ref(loadingComponent)

  return {
    setup() {
      loader().then(
        (comp) => {
          if (comp && comp[Symbol.toStringTag] === 'Module') {
            comp = comp.default
          }
          component.value = comp
        },
        () => {
          component.value = errorComponent
        }
      )

      return () => h(component.value)
    }
  }
}

初始化时先渲染 loadingComponent。

加载成功后,切换为异步组件;加载失败后,切换为 errorComponent。

3. 支持 timeout

timeout 用来限制异步组件的加载时间。

把原来的 loader 调用封装成 loadComponent,同时监听定时器和加载函数返回的 Promise:

function loadComponent() {
  return new Promise((resolve, reject) => {
    if (timeout && timeout > 0) {
      setTimeout(() => {
        reject(new Error('Async component timed out'))
      }, timeout)
    }

    loader().then(resolve, reject)
  })
}

当 loader 加载成功时,Promise 会进入成功状态;当加载失败或超过指定时间时,Promise 会进入失败状态。

由于 Promise 的状态一旦确定就不会再次改变,即使超时和加载完成几乎同时发生,也只会处理第一次触发的结果。