Vue3动态组件切换卡顿怎么办?能不能保持状态、懒加载还兼顾性能优化?
最近帮同事处理Vue3后台管理系统的标签页切换时,碰到了两个挺典型的问题:一个是切换复杂列表页再切回来,刚才筛选的条件、滚动的位置全丢了,体验特别差;另一个是系统有几十个自定义表单组件,全部预加载导致首屏加载慢到离谱,后来查了点官方文档,踩了几个小坑才解决,今天就借着这些实战经验,聊聊Vue3动态组件的核心玩法、状态保持的细节、懒加载的实现,还有怎么结合常用库做全方位优化。
先说说Vue3动态组件到底是个啥
如果你之前用Vue2做过标签页、轮播图、多步骤表单这种内容切换频繁但结构固定在一个容器里的场景,肯定见过<component :is="当前组件"></component>这个写法,Vue3的动态组件本质上和Vue2没差——就是靠is绑定的变量值,动态渲染对应的组件。
不过Vue3的变化在于组件注册和类型支持上更灵活了,先说组件注册,以前动态组件要么全局注册,要么得在当前组件的components选项里把所有可能用到的都列一遍;现在Vue3可以直接把组件对象赋值给is的变量,甚至可以是一个异步组件的函数调用结果,比如后台系统的标签页,我们完全可以从路由配置里抽取出对应路由的组件对象,直接传给动态标签页的容器,不用在父组件里重复写注册逻辑,代码一下子清爽很多,类型支持方面,用TypeScript开发的话,Vue3官方提供了Component泛型,给is绑定的变量加类型注解,能避免不小心传错组件名或者组件对象的低级错误。
切换后状态全丢?Vue3 keep-alive帮你“留一手”
刚才说的筛选条件、滚动位置丢失的问题,核心就是Vue3默认切换组件时会把旧组件销毁,再挂载新组件,旧组件的data、ref、watch这些状态自然就没了,这时候就要请出keep-alive组件了——它是Vue3内置的抽象组件,不会渲染成真实的DOM节点,作用就是把包裹的组件的状态缓存下来,下次切换回来时直接复用旧实例,不用重新挂载。
不过keep-alive不是随便套个标签就行的,有几个细节特别容易踩坑:
第一个是缓存组件的选择,不是所有组件都需要缓存的,比如一次性的提示弹窗、简单的静态展示页,缓存反而浪费内存,这时候可以用keep-alive的include和exclude属性来控制,这两个属性都支持三种写法:字符串(组件名,注意是组件的name选项值,不是文件路径)、数组(多个组件名)、正则表达式,但正则表达式和数组必须用v-bind绑定,字符串可以直接写,这里有个小提醒:如果用Vue3的<script setup>语法写组件,默认是没有name选项的,得用defineOptions宏定义一个,不然include和exclude根本找不到对应的组件。
第二个是缓存的生命周期钩子变化,被keep-alive包裹的组件,第一次挂载时会触发onMounted,但被切走再切回来时,不会再触发onMounted和onBeforeMount,而是会触发onActivated;被切走时也不会触发onUnmounted和onBeforeUnmount,而是会触发onDeactivated,这一点很重要!比如刚才说的列表页滚动位置,直接在onMounted里监听滚动事件或者恢复位置是没用的,得把恢复位置的逻辑放到onActivated里,监听事件清理的逻辑放到onDeactivated里(避免内存泄漏)。
第三个是keep-alive的最大缓存数,如果用keep-alive包裹几十个复杂组件,内存占用肯定会飙升,甚至导致浏览器卡顿,这时候可以用max属性设置最大缓存数,比如后台系统的标签页一般设置10个左右,当缓存数超过max时,keep-alive会遵循LRU(最近最少使用)算法,把最久没被激活的组件销毁,释放内存。
几十个组件全预加载?试试Vue3异步动态组件
再说说首屏加载慢的问题,后台系统有几十个自定义表单组件是常有的事,要是全部打包在首屏的JS文件里,JS体积会大到好几兆,加载速度自然慢,这时候就要结合动态组件和Vue3的异步组件了——异步组件不会在首屏打包,而是在第一次用到的时候才通过网络请求加载对应的JS文件。
Vue3的异步组件有两种写法:基础写法和高级写法,基础写法就是用defineAsyncComponent宏,传一个返回Promise的函数,这个Promise的resolve值就是组件对象,比如自定义表单组件,我们可以把每个表单组件都单独拆成一个文件,然后在父组件里定义一个异步组件的工厂函数,根据表单类型返回对应的defineAsyncComponent结果,再把这个结果赋值给动态组件的is变量,这样只有当第一次切换到某个表单类型的标签页时,对应的表单组件JS才会加载。
高级写法就更实用了,可以处理加载状态、错误状态、加载超时、重试等情况,比如用户打开一个需要加载大组件的标签页,可能会等几秒,这时候如果没有加载状态,用户会以为页面卡死了,直接关掉;要是加载失败了,也得给用户一个友好的提示,并且提供重试按钮,高级写法里,defineAsyncComponent传的不是一个简单的返回Promise的函数,而是一个配置对象,里面有loader(和基础写法的函数一样)、loadingComponent(加载时显示的组件)、errorComponent(加载失败时显示的组件)、delay(延迟显示加载组件的时间,比如设置200ms,这样如果组件加载很快,就不会一闪而过加载动画)、timeout(加载超时时间,超过这个时间就显示错误组件)、onError(加载失败时的回调函数,可以在这里处理重试逻辑),这些配置能大大提升用户体验,特别是网络不好的情况。
全方位优化Vue3动态组件的性能
除了keep-alive和异步组件,还有几个小技巧能进一步优化动态组件的性能:
第一个是给动态组件加唯一的key属性,很多开发者觉得动态组件有is就行,不需要key,但其实如果is绑定的变量有时候是同一个组件名,但对应的props或者组件内部的某些依赖变化了,Vue3可能会复用旧的DOM节点,导致渲染异常,加唯一的key属性(比如结合组件名和当前路由的query参数、或者当前的唯一ID),能告诉Vue3什么时候需要重新创建组件实例,什么时候可以复用,既能避免渲染异常,又能提升复用的效率。
第二个是拆分组件,减少单个组件的复杂度,如果一个动态组件是个几千行代码的大组件,哪怕用了异步组件,第一次加载的时候也会慢,这时候可以把大组件拆成更小的子组件,子组件再按需异步加载,这样单个动态组件的体积就小了,加载和渲染速度都会提升。
第三个是结合虚拟滚动处理长列表动态组件,如果动态组件里有长列表(比如刚才说的筛选列表),哪怕用了keep-alive保持滚动位置,渲染几千条数据的DOM节点也会慢,这时候可以用Vue3官方推荐的虚拟滚动库,只渲染当前可视区域的DOM节点,这样不管列表有多少条数据,都能保持流畅的滚动和切换。
第四个是关闭开发模式的严格检查,开发模式下Vue3会做很多严格检查,比如组件的props、emits是否符合定义,这些检查会影响性能,在生产环境打包时,记得用Vue CLI或者Vite的生产模式配置,把这些严格检查关掉,能提升一点运行时的性能。
实战一下:做一个带缓存、带异步加载的标签页
刚才说了这么多理论,现在结合后台系统的标签页做个简单的实战,代码用Vue3的<script setup>语法和TypeScript写,方便大家理解:
首先我们需要定义标签页的数据结构,包括标签页的id、标题、组件对象(这里是异步组件的工厂函数结果)、关闭状态这些;然后需要一个keep-alive包裹的动态组件容器;接着需要处理标签页的添加、切换、关闭逻辑;最后处理keep-alive的缓存清理逻辑(当关闭某个标签页时,要手动把对应的缓存实例从keep-alive里删掉,不然设置了max属性还好,没设置的话内存会一直占用)。
手动清理keep-alive缓存的方法,Vue3官方没有提供直接的API,但我们可以通过ref获取keep-alive组件的实例,然后访问它的cache和keys属性来手动删除。cache属性是一个Map,key是组件的key值,value是组件的VNode;keys属性是一个Set,保存了所有缓存组件的key值,当关闭某个标签页时,我们先从keys里删掉对应的key,再从cache里删掉对应的VNode,这样下次再打开这个标签页时,就会重新创建组件实例了。
总结一下
Vue3动态组件是处理内容切换频繁场景的利器,结合keep-alive能保持组件状态,结合异步组件能减少首屏加载时间,再加几个小技巧就能全方位优化性能,不过在使用的时候,要注意组件的name选项、缓存组件的选择、缓存的生命周期钩子、手动清理缓存这些细节,避免踩坑,如果你还有其他关于Vue3动态组件的问题,或者有更好的优化技巧,欢迎在评论区留言一起讨论。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


