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

为什么用了懒加载还是觉得页面没快多少?Vue3里到底该怎么把组件懒加载用明白?

terry 1小时前 阅读数 28 #Vue

大家刚开始接触Vue3组件懒加载时,大概率会遇到一个小尴尬:跟着教程写了defineAsyncComponent或者路由的component改成箭头函数引入,但刷新F12看网络请求,首屏还是加载了一堆不该出来的文件,或者加载速度只有毫秒级的微提升,完全没达到预期的分块降重效果,今天咱们就把Vue3懒加载的底层逻辑、正确姿势、进阶技巧和避坑点全捋一遍,争取让每一行懒加载代码都能实实在在干活。

先搞懂:Vue3的组件懒加载到底是啥原理

很多教程会说“懒加载就是按需加载,不用就不加载”,但这句话太浅了,踩坑的根源往往就是只懂表面。

我们可以把Vue3的项目打包想象成打包快递:普通加载是把所有东西塞在一个超大箱子里(首屏的app.js或者bundle.js),寄到用户手里要等很久拆封;懒加载呢,就是把每个暂时不用的组件单独打包成小快递盒,只有用户触发了某个条件——比如点开了某个Tab、滚动到了某个区域、点击了某个弹窗按钮——才会让快递小哥(浏览器)去取对应的小盒子。

具体到技术实现上,Vue3靠的是Webpack/Vite的代码分割功能:defineAsyncComponent或者路由懒加载的写法,本质上是给打包工具留了个「标记」,告诉它“别把这个组件塞主包,单独分成一个chunk文件”;等用户触发条件时,Vue3再通过动态import(ES6+的原生语法)向浏览器发起请求,加载对应的chunk文件,加载完成后挂载到DOM上。

这里有个关键点要注意:不管是Webpack还是Vite,代码分割的触发条件都不是看你用没用到Vue3的API,而是看你有没有用动态import的形式引入组件——比如你写了import { defineAsyncComponent } from 'vue'; const MyModal = defineAsyncComponent(() => import('./MyModal.vue')),这才是真的给打包工具递了标记;如果你只是写了个普通函数返回组件,或者静态import进来再传给defineAsyncComponent,那打包工具根本不会理你,还是会塞主包。

最基础但90%的人会写错的:Vue3基础组件懒加载

所谓基础组件,就是非路由的、需要手动触发才显示的组件——比如弹窗、抽屉、悬浮广告、Tab切换的内容面板这些。

很多人刚写基础组件懒加载时,会犯这两个低级错误:要么在模板里直接写死组件名忘了条件渲染,要么动态import写得不对触发不了代码分割。

先看第一个错误:比如你写了const MyTab1 = defineAsyncComponent(() => import('./MyTab1.vue')); const MyTab2 = defineAsyncComponent(() => import('./MyTab2.vue')),然后模板里直接<my-tab1 /><my-tab2 />,哪怕你用v-show隐藏了另一个,浏览器还是会在首屏触发两个动态import?不对不对——v-show只是display:none,组件已经被解析并尝试挂载了,defineAsyncComponent会自动在组件首次被解析挂载时发起请求,不管你显不显示,所以基础组件懒加载必须配v-if,不能配v-show!v-if是“条件满足才解析渲染组件”,刚好和懒加载的触发逻辑完美契合。

再看第二个错误:静态import转动态import,比如你写了import MyModal from './MyModal.vue'; const LazyModal = defineAsyncComponent(() => Promise.resolve(MyModal))——这种写法里,MyModal已经在静态import阶段被塞到主包了,动态Promise.resolve只是绕了个弯,打包工具根本不会生成单独的chunk,完全白忙活,正确的写法应该是直接把() => import('./MyModal.vue')传给defineAsyncComponent,或者写成具名导出的形式(比如MyModal.vue里用了export { default as MyModal }或者具名导出组件,动态import就写成() => import('./MyModal.vue').then(mod => mod.MyModal))。

最常用但容易忽略细节的:Vue3路由懒加载

路由懒加载应该是大家用得最多的场景了,毕竟单页应用(SPA)的路由如果不做懒加载,首屏可能要加载几十上百个页面的组件,打包体积轻松破几十MB。

Vue3路由懒加载的写法其实很简单,不管是Vue Router 3升级到4的兼容写法,还是Vue Router 4的原生写法,都逃不开动态import,比如兼容写法是component: () => import('./views/Home.vue'),原生写法其实也差不多——Vue Router 4内部已经自动帮你处理了,不需要你再手动包defineAsyncComponent。

但路由懒加载的细节才是决定优化效果的关键:

第一个细节是「分组懒加载」,比如你的项目里有「用户模块」(登录、注册、个人中心)、「商品模块」(商品列表、商品详情、购物车)、「后台管理模块」(用户管理、商品管理、订单管理),如果每个路由都单独打包成一个chunk,那用户点登录、再点注册、再点个人中心,会发起3次网络请求,反而可能影响体验;如果把同一个模块的路由打包成一个chunk,那用户第一次点用户模块的任意页面,只需要发起1次请求,后续切换同模块页面就不用再请求了,分组懒加载的写法也很简单,就是在动态import里加个注释标记:Webpack是/* webpackChunkName: "user" */,Vite是/* @vite-ignore */(其实Vite默认会自动根据路由的目录结构分组,但加注释可以手动指定分组名,更可控),比如Webpack的分组写法:component: () => import(/* webpackChunkName: "user" */ './views/Login.vue'),这样所有加了user标记的路由都会被打包成一个叫user.[hash].js的chunk。

第二个细节是「首屏路由别懒加载」,很多人为了追求极致的代码分割,会把首页、登录页这种首屏就可能打开的路由也做懒加载——这完全是反效果!首屏路由本来就是用户进来就看的,你把它打包成单独的chunk,反而会让浏览器多发起1次网络请求,增加首屏加载时间,所以首屏路由(比如Home.vue、Login.vue)一定要静态import进来,不要做懒加载。

第三个细节是「路由懒加载的错误处理和加载状态」,很多人写路由懒加载时,只会写一个component箭头函数,一旦网络不好或者chunk文件加载失败,页面就会白屏,用户体验极差,Vue Router 4其实内置了路由懒加载的加载状态和错误状态处理:你可以在路由配置里加loadingComponenterrorComponent选项,加载时显示loading组件,加载失败显示错误组件;或者你也可以手动用defineAsyncComponent包一层,自定义更复杂的加载和错误逻辑(比如重试机制)。

进阶技巧:让Vue3懒加载更智能

除了基础的v-if触发和路由触发,Vue3还有几种更智能的懒加载触发方式,这些方式能让你的页面加载体验再上一个台阶。

第一种是「滚动触发懒加载」,比如你的页面底部有个「热门推荐」组件,只有用户滚动到页面底部附近时才会显示——这时候你可以用IntersectionObserver API来触发动态import,不需要用户手动点击或者v-if,IntersectionObserver是浏览器原生的API,专门用来监听元素是否进入视口,性能比监听scroll事件好太多(因为它是异步的,不会阻塞主线程),Vue3里用IntersectionObserver触发懒加载的思路是:先写一个空的占位元素(比如div),然后给这个占位元素加一个IntersectionObserver,当占位元素进入视口时,再动态import对应的组件,替换掉占位元素,你也可以直接用第三方库,比如vue-lazyload或者@vueuse/core里的useIntersectionObserver,封装得更简单,但如果只是简单的滚动触发,自己写原生的IntersectionObserver也很快,还能减少第三方库的依赖。

第二种是「预加载懒加载」,比如你的首页有个「去商品列表」的按钮,根据用户的浏览习惯,80%的用户点完首页都会去商品列表——这时候你可以在用户 hover 到「去商品列表」按钮时,就预加载商品列表的chunk文件,等用户真的点击按钮时,商品列表已经加载得差不多了,切换速度会非常快,预加载的写法也很简单:就是在按钮的mouseenter事件里,手动调用一下动态import的函数,但不要把返回的Promise挂载到组件上,只是单纯地让浏览器去下载对应的chunk文件,浏览器会自动把下载好的文件缓存起来,等后续真正需要用时,直接从缓存里取,不会再发起网络请求。

第三种是「条件预加载懒加载」,比如你的后台管理系统,只有管理员权限的用户才能进入「订单管理」页面——这时候你可以在用户登录后、判断出是管理员权限时,就预加载「订单管理」模块的chunk文件,普通用户就不用加载了,能进一步减少不必要的网络请求和缓存占用。

避坑指南:Vue3懒加载的那些常见坑

刚才在基础和进阶部分已经提到了一些坑,这里再集中整理一下,避免大家踩雷:

  1. v-if vs v-show:基础组件懒加载必须配v-if,v-show会触发组件解析和动态import,白忙活。
  2. 静态import转动态import:动态import必须是直接的() => import('./xxx.vue'),不能静态import进来再转Promise.resolve,否则不会生成单独的chunk。
  3. 首屏路由懒加载:首屏路由一定要静态import,别做懒加载,否则会增加首屏加载时间。
  4. 没有处理加载状态和错误状态:不管是基础组件还是路由懒加载,一定要加加载组件和错误组件,否则网络不好时会白屏。
  5. 分组懒加载太细或太粗:分组太细会导致网络请求太多,分组太粗会导致单个chunk文件太大,建议根据模块、用户权限、浏览习惯等因素合理分组。
  6. 过度使用懒加载:不是所有组件都适合懒加载——比如页面顶部的导航栏、底部的页脚,这些是每个页面都要用的,直接静态import就行;比如一些体积很小的组件(只有几KB),单独打包成chunk文件反而会因为网络请求的 overhead(请求头、握手等)浪费时间,不如直接塞主包。
  7. 忽略浏览器缓存:动态import生成的chunk文件,浏览器默认会缓存,所以你可以给chunk文件加一个hash值(Webpack和Vite默认都会加),这样每次更新代码时,hash值都会变,浏览器会重新下载最新的文件,不会用旧的缓存;同时你也可以设置chunk文件的缓存时间,比如设置成长期缓存(一年),因为hash值变了就会重新下载,长期缓存能减少用户后续访问的加载时间。

怎么判断Vue3懒加载有没有生效?

很多人写完懒加载后,不知道有没有生效——其实很简单,打开浏览器的F12开发者工具,切换到「Network」(网络)面板,然后刷新页面或者触发懒加载条件,看有没有生成新的chunk文件请求:

如果是Webpack打包的项目,chunk文件的名字通常是「[name].[hash].js」或者「[id].[hash].js」,比如刚才的user模块,就会生成「user.abc123.js」;如果是Vite打包的项目,chunk文件的名字通常是「assets/[name]-[hash].js」,也很容易识别。

你也可以切换到「Sources」(源文件)面板,看有没有单独的懒加载组件的源文件——如果有的话,说明代码分割生效了,懒加载也就成功了一半;另一半就是看触发条件时,组件能不能正常显示。

好啦,关于Vue3懒加载组件的所有内容就说到这里了,其实懒加载的核心就是「按需加载」,但要把它用明白,还是要搞懂底层的代码分割原理,注意细节,合理避坑,再配合一些进阶技巧,才能让页面加载速度有质的提升,大家可以回去翻翻自己的项目,看看有没有写错的懒加载代码,赶紧改过来试试看效果吧!

版权声明

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

热门