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

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

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

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

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


前言

在 Vue3 里,我们经常用 defineAsyncComponent 来做组件懒加载,比如页面特别大、组件依赖的第三方库比较重,就可以按需加载。按理说,Vue3 官方文档说得很清楚:在加载过程中,可以显示一个 loadingComponent,加载失败还能显示 errorComponent

但实际在 Nuxt3 环境下,很多同学发现了一个问题:无论设置 loadingComponent 还是 errorComponent 都不生效。看起来配置都对,可就是没效果。

那到底是为什么?接下来我们就把问题复现、分析,然后给出最终的解决方案。

问题复现

来看一段最小示例代码:

<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>`
}

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
})
</script>

在这段代码里,我特意模拟了一个 10 秒的延迟加载

  • 按理说,前 3 秒应该显示 LoadingComponent
  • 超过 3 秒后应该报错,显示 ErrorComponent

但是运行后你会发现:页面什么都没显示,loading 和 error 都失效了

原因分析

这里踩坑的关键点有两个:

  1. Nuxt3 默认使用 Suspense 机制

    • 在 Nuxt3 里,所有异步组件都被包在 <Suspense> 里。
    • 这意味着你的 loadingComponenterrorComponent 会被 Suspense 吞掉。
    • Suspense 的逻辑是:要么渲染成功,要么 fallback,不会触发 defineAsyncComponent 自己的 loading/error 机制。
  2. 渲染函数 vs. 模板组件写法的差异

    • defineAsyncComponent 的配置中,如果 loadingComponenterrorComponent 用的是对象模板(template: 'xxx'),在 Nuxt3 SSR 环境里并不会被正常渲染。
    • 但如果换成渲染函数(h('div', {}, '内容')),就能显示出来。

所以,最终解决方案就是:

  • 设置 suspensible: false,让异步组件跳出 Nuxt 的 Suspense 逻辑。
  • 改用渲染函数来定义 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 // 关键配置:禁用 Suspense 托管
})
</script>

这样一来:

  • 页面加载时,立刻显示 “加载中…”
  • 超过 3 秒后,显示 “加载失败,请重试”
  • 如果 10 秒后组件真的加载成功,正常渲染出来

实际场景应用

想象一个真实业务场景:

你在 Nuxt3 里做了一个后台管理系统,其中有一个数据可视化模块,依赖 echarts 这种大体积的库。如果用户一进页面就加载 echarts,可能会卡半天。

这时候就可以用 defineAsyncComponent 来做懒加载:

  • 页面先展示 “加载中…”
  • 如果网络太慢,超过 3 秒还没好,就告诉用户 “加载失败,请刷新重试”
  • 如果一切正常,组件加载成功再显示图表

这样既能优化用户体验,又能避免页面空白。

总结

Nuxt3 下 defineAsyncComponent 的 loading 和 error 不生效,主要是因为 被 Suspense 吞掉

解决办法:

  1. suspensible: false 让异步组件脱离 Suspense 托管
  2. loadingComponenterrorComponent 用渲染函数写法
  3. 在实际业务中,可以结合超时设置 timeout,提升用户体验

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

Logo

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

更多推荐