网罗开发 (小红书、快手、视频号同名)

  大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。

图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG

我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。

展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!


前言

在 Vue3 里,defineAsyncComponent 是个特别好用的 API。它能帮我们把一些体积大的组件做成懒加载,按需加载,页面首屏体验会好很多。比如说我们有个图表页面依赖 echarts,就完全没必要一开始就把它打包进来。

不过我最近在 Nuxt3 项目里踩了个坑:我按照官方文档写了 loadingComponenterrorComponent,结果发现完全没生效!页面空空的,既没有显示 “加载中…”,也没有显示 “加载失败”。

下面我来详细复现一下问题,然后再带大家分析为什么会这样,最后给出解决方案。

问题复现

我写了这样一个示例页面:

<template>
  <div>
    <h2>Async Component 示例</h2>
    <!-- 懒加载组件 -->
    <AsyncHelloWorld />
  </div>
</template>

<script setup lang="ts">
import { defineAsyncComponent } from 'vue'

// 定义一个加载时的占位组件
const LoadingComponent = {
  template: `<div style="padding: 10px; color: #666;">加载中...</div>`
}

// 定义一个错误时的占位组件
const ErrorComponent = {
  template: `<div style="padding: 10px; color: red;">加载失败,请重试</div>`
}

// 使用 defineAsyncComponent 懒加载目标组件
const AsyncHelloWorld = defineAsyncComponent({
   loader: () => new Promise((resolve) => {
            setTimeout(() => {
                import('vue3-colorpicker').then(({ColorPicker}) => {
                    resolve(ColorPicker)
                })
            }, 10000) // 模拟 10 秒加载
        }),
  loadingComponent: LoadingComponent,
  errorComponent: ErrorComponent,
  delay: 0,
  timeout: 3000 // 超过 3 秒报错,显示 errorComponent
})
</script>

逻辑很简单:

  • 模拟一个 10 秒的组件加载
  • 前 3 秒应该显示 loadingComponent
  • 超过 3 秒应该显示 errorComponent

结果运行后发现:啥都没有显示,页面直接空白。

原因分析

这里有两个关键点:

1. Nuxt3 默认会把异步组件放进 Suspense

在 Nuxt3 里,所有异步组件都会被 <Suspense> 包裹。

Suspense 的逻辑是这样的:要么等异步加载完成,要么就走 fallback 插槽。它不会让 defineAsyncComponent 自带的 loadingComponenterrorComponent 接管渲染流程。

换句话说,Nuxt3 的 Suspense 和 Vue3 的 loadingComponent/errorComponent 机制冲突了。

2. SSR 环境下 template 写法不生效

在 Nuxt3 的 SSR 环境里,如果你用 template: '<div>xxx</div>' 这种对象定义组件,有时候不会被渲染出来。改用渲染函数(h)写法,就能稳定显示。

所以根本原因就是:

  • Nuxt3 默认 Suspense 托管 → 吞掉了 loading/error。
  • 占位组件 template 写法在 SSR 下表现异常。

解决方案

最终的解决办法有两个关键点:

  1. 设置 suspensible: false,让组件跳出 Suspense 托管。
  2. 用渲染函数写 loadingComponenterrorComponent

完整代码如下:

<template>
  <div>
    <h2>Async Component 示例</h2>
    <AsyncHelloWorld />
  </div>
</template>

<script setup lang="ts">
import { defineAsyncComponent, h } from 'vue'

// 定义一个加载时的占位组件
const LoadingComponent = {
  render() {
    return h('div', { style: 'padding: 10px; color: #666;' }, '加载中...')
  }
}

// 定义一个错误时的占位组件
const ErrorComponent = {
  render() {
    return h('div', { style: 'padding: 10px; color: red;' }, '加载失败,请重试')
  }
}

// 使用 defineAsyncComponent
const AsyncHelloWorld = defineAsyncComponent({
  loader: () =>
    new Promise((resolve) => {
      setTimeout(() => {
        import('vue3-colorpicker').then(({ ColorPicker }) => {
          resolve(ColorPicker)
        })
      }, 10000)
    }),
  loadingComponent: LoadingComponent,
  errorComponent: ErrorComponent,
  delay: 0,
  timeout: 3000,
  suspensible: false // 关键配置
})
</script>

这样运行就能看到效果:

  • 进入页面时立刻显示 “加载中…”
  • 超过 3 秒没加载成功,就显示 “加载失败,请重试”
  • 10 秒后加载成功,渲染出目标组件

实际业务场景

举个例子,如果你的后台管理系统里有一个数据分析页面,依赖 echarts 或者 three.js 这种大体积的库:

  • 如果不做懒加载,首屏加载会非常慢,用户体验很差;
  • 如果用 defineAsyncComponent,就能做到:页面先显示 “加载中…”,失败时提示用户重试,成功时再渲染。

这套机制不仅能优化用户体验,还能提升系统的健壮性。

总结

在 Nuxt3 里,defineAsyncComponent 的 loadingComponent 和 errorComponent 默认不生效,原因是 被 Suspense 吞掉,再加上 SSR 环境下 template 写法有兼容性问题。

解决方案就是:

  1. 配置 suspensible: false
  2. 用渲染函数 h 写 loading 和 error

这样就能在 Nuxt3 里优雅地使用懒加载组件。

Logo

加入「COC·上海城市开发者社区」,成就更好的自己!

更多推荐