Code前端首页关于Code前端联系我们

一、Vue3异步组件是啥?和普通组件有啥区别?

terry 2天前 阅读数 27 #Vue
文章标签 Vue3 异步组件

很多刚开始学Vue3的小伙伴,看到“异步组件”这词就犯嘀咕:这玩意儿到底是干啥的?项目里啥时候用、怎么用才能让页面加载更快?今天咱就从基础概念到实战技巧,把Vue3异步组件彻底讲明白,不管是优化首屏性能还是拆分大代码包,看完这篇心里有数~

先搞懂“异步”在哪。**普通组件**是项目打包时,就把组件代码塞到最终的JS文件里;而**异步组件**是“等需要用的时候再加载”,比如用户点了某个按钮、进入某个路由时,才去加载对应的组件代码。

举个例子:做一个后台管理系统,首页有统计卡片,还有个“报表”页面(里面有复杂图表,代码体积很大),如果用普通组件,用户打开首页时,报表的代码也会被打包进来,导致首页加载变慢;但用异步组件的话,首页加载时只加载首页需要的代码,“报表”组件的代码等用户点进去再加载,这样首屏加载速度就快多了。

简单说,异步组件是Vue给咱的“按需加载”工具,核心作用是拆分代码包,优化首屏性能

啥场景下必须用异步组件?这3个场景别错过

知道了概念,得明白啥时候用,这几个场景用异步组件,效果立竿见影:

首屏性能优化(最常见)

用户打开网页,最直观的是“首屏加载快不快”,如果页面里有大组件(比如带复杂动画的轮播图、第三方富文本编辑器),把这些组件改成异步加载,初始JS文件体积能减少一大截,首屏加载速度(像FCP、LCP这些性能指标)能明显提升。

代码分包,避免单个JS文件过大

项目越做越大,打包后的JS文件可能几百KB甚至几MB,用异步组件把大组件、第三方库(比如ECharts、Quill)单独分包,每个分包按需加载,既方便缓存,也能降低服务器压力。

路由懒加载(和Vue Router配合)

Vue Router的“路由懒加载”本质就是异步组件!比如配置路由时:

const router = createRouter({
  routes: [
    { 
      path: '/report', 
      component: () => import('./views/Report.vue') 
    }
  ]
})

这里的() => import(...)就是异步加载组件,用户访问/report时才加载对应的组件代码,首页不用背这个“包袱”。

Vue3异步组件基础:defineAsyncComponent怎么玩?

Vue3里用defineAsyncComponent来创建异步组件,它有两种用法:简单版和复杂版(带加载、错误状态处理)。

简单用法:直接传加载函数

最基础的写法,把组件的导入逻辑包成一个函数传给defineAsyncComponent

import { defineAsyncComponent } from 'vue'
// 异步加载AsyncComp.vue
const AsyncComp = defineAsyncComponent(() => import('./components/AsyncComp.vue'))

这样就创建了一个异步组件,用的时候和普通组件一样:<AsyncComp />

复杂用法:配置对象(处理加载、错误状态)

如果想在组件加载时显示“加载中”,加载失败显示“重试”,就得用配置对象:

const AsyncCompWithStatus = defineAsyncComponent({
  // 核心:加载组件的函数,返回Promise
  loader: () => import('./components/BigComponent.vue'),  
  // 加载过程中显示的组件
  loadingComponent: Loading,  
  // 加载失败后显示的组件
  errorComponent: ErrorComp,  
  // 延迟多久显示loading(毫秒),避免网络快时loading闪一下
  delay: 200,  
  // 超时时间(毫秒),超过这个时间没加载完就显示error
  timeout: 5000  
})

这里每个配置项都有讲究:

  • delay:比如用户网络超好,组件100ms就加载完了,加个200ms延迟,loading组件就不会“闪一下”,体验更丝滑;
  • timeout:防止网络极差时一直卡加载,超过5秒就提示“加载失败”,让用户能重试。

进阶:异步组件和Suspense咋配合?

Vue3有个叫<Suspense>的内置组件,专门用来处理多个异步组件的加载状态,它和defineAsyncComponent搭配,能更灵活地控制加载过程。

Suspense的基本用法

<Suspense>包裹异步组件,通过#default#fallback插槽分别指定“加载完成显示的内容”和“加载中显示的内容”:

<template>
  <Suspense>
    <!-- 加载完成后显示的组件 -->
    <template #default>
      <AsyncComp />
      <AnotherAsyncComp /> <!-- 多个异步组件也能一起处理 -->
    </template>
    <!-- 加载过程中显示的内容 -->
    <template #fallback>
      <LoadingSpinner />
    </template>
  </Suspense>
</template>

这样不管有多少个异步组件,只要没加载完,就显示fallback里的加载动画;全加载完了,再显示default

和defineAsyncComponent的区别

defineAsyncComponent单个组件级别的加载/错误处理(比如给某个组件单独配loading、error);而<Suspense>批量处理多个异步组件的加载状态,适合页面级的加载控制,实际项目里,两者经常配合着用~

性能优化:异步组件还能这么玩!

只会基础用法可不够,这些优化技巧能让异步组件更“丝滑”:

预加载:提前加载,提升交互速度

用户还没点某个按钮,但你知道他大概率会点(比如鼠标hover到按钮上),这时候可以提前加载异步组件,等用户真点的时候,组件已经加载好了,体验起飞!

举个例子,在路由守卫里预加载报表组件:

router.beforeResolve((to, from, next) => {
  if (to.name === 'Report') {
    // 预加载Report.vue,返回Promise但不渲染
    import('./views/Report.vue') 
  }
  next()
})

或者用HTML的<link rel="preload">提前加载JS文件,不过这种方式得自己管理资源,相对麻烦些。

缓存策略:避免重复加载

异步组件默认每次触发加载都会重新请求JS文件,有时候没必要(比如组件代码没更新),可以用webpack的缓存配置,或者自己存加载后的组件:

// 自己维护缓存
const cache = new Map()
const AsyncCompWithCache = defineAsyncComponent(() => {
  if (cache.has('AsyncComp')) {
    return cache.get('AsyncComp')
  }
  const promise = import('./components/AsyncComp.vue')
  cache.set('AsyncComp', promise)
  return promise
})

这样第二次加载时,直接用缓存里的Promise,不用重复请求。

结合CDN:把异步组件包丢给CDN

如果项目用了CDN加速,把异步组件的分包传到CDN上,用户加载时从CDN拉取,既减轻自己服务器压力,又能利用CDN的缓存和加速能力~

底层原理:异步组件咋实现“按需加载”?

想深入理解的同学,得扒扒底层逻辑:

动态import():代码拆分的关键

异步组件能按需加载,核心是ES的动态import()语法,webpack、Vite这些打包工具看到import(),会把里面的模块单独打成一个JS文件(叫chunk),比如import('./components/AsyncComp.vue'),webpack会生成一个单独的chunk,命名可能是asyncComp.[hash].js

Vue的封装:defineAsyncComponent做了啥?

defineAsyncComponent把“加载组件”的过程包成一个Promise,在组件渲染时:

  • 先执行loader函数(也就是import()),开始加载chunk;
  • 加载过程中,渲染loadingComponent
  • 加载成功,渲染目标组件;
  • 加载失败(或超时),渲染errorComponent

Suspense的原理:收集Promise并等待

<Suspense>的核心是收集子组件里的Promise(比如异步组件的加载Promise),等所有Promise都resolve了,再渲染default插槽;没resolve时,渲染fallback插槽,这也是它能批量处理多个异步组件的原因~

实战:用异步组件优化“大型表单页”

光说不练假把式,来看个真实案例:优化一个包含富文本编辑器的表单页。

场景描述

表单页有输入框、下拉选择器,还有个富文本编辑器(用了第三方库Quill,体积很大),如果把富文本编辑器当普通组件,初始JS包会包含Quill的代码,导致首屏加载慢。

改造步骤

  1. 把富文本编辑器改成异步组件,用defineAsyncComponent配置加载、错误状态:
    import { defineAsyncComponent } from 'vue'
    import Loading from './Loading.vue'
    import ErrorView from './ErrorView.vue'

// 异步加载富文本组件(内部用了Quill) const AsyncRichText = defineAsyncComponent({ loader: () => import('./RichText.vue'), loadingComponent: Loading, errorComponent: ErrorView, delay: 300, // 延迟300ms显示loading timeout: 8000 // 8秒超时 })


2. **在表单页面中使用异步组件**:  
```vue
<template>
  <div class="form-page">
    <input v-model="title" placeholder="标题" />
    <select v-model="type">
      <option value="1">类型1</option>
      <option value="2">类型2</option>
    </select>
    <!-- 富文本编辑器异步加载 -->
    <AsyncRichText v-model="content" />
    <button @click="submit">提交</button>
  </div>
</template>
<script setup>
import { ref } from 'vue'
import AsyncRichText from './AsyncRichText'
const title = ref('')
const type = ref('1')
const content = ref('')
const submit = () => {
  // 提交逻辑...
}
</script>

效果

改造后,初始JS包不包含Quill和RichText的代码,首屏加载速度提升;用户滚动到富文本区域(或组件渲染时)才加载对应的代码,加载时显示loading,失败了能重试,体验和性能都拉满~

常见问题答疑

最后解决几个高频问题,避免踩坑:

异步组件里能⽤Provide/Inject吗?

能!异步组件加载后也是正经的Vue组件实例,和普通组件一样能⽤Provide/Inject传值。

加载失败后咋重试?

可以在errorComponent里加“重试”按钮,触发重新加载,比如给errorComponent传个retry函数:

// ErrorView.vue
<template>
  <div>加载失败!<button @click="retry">重试</button></div>
</template>
<script setup>
const props = defineProps(['retry']) // 接收retry函数
</script>
// 异步组件配置
const AsyncComp = defineAsyncComponent({
  loader: () => import('./AsyncComp.vue'),
  errorComponent: ErrorView,
  // 给errorComponent传retry函数
  onError: (error, retry) => {
    // 把retry传给ErrorView
    ErrorView.retry = retry 
  }
})

Vue Router的懒加载和异步组件啥关系?

Vue Router的component: () => import(...)本质就是异步组件!Vue Router内部帮咱做了defineAsyncComponent的封装,所以能直接用。

loadingComponent能异步加载吗?

必须能!

const AsyncComp = defineAsyncComponent({
  loader: () => import('./BigComponent.vue'),
  loadingComponent: () => import('./Loading.vue'), // loading组件自己也异步加载
  errorComponent: ErrorView
})

这样loading组件的代码也会被单独分包,进一步减小初始包体积~

看完这些,再遇到Vue3异步组件是不是不慌了?记住核心:按需加载、优化性能,结合defineAsyncComponent和Suspense玩出花,再加上预加载、缓存这些技巧,项目里的首屏速度和代码拆分肯定能更上一层楼~要是还有细节没搞懂,评论区随时喊我,咱再唠!

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门