想转Vue3开发?别踩这10个没人直接说透的坑+进阶指南
现在前端圈讨论框架优化,绕不开的核心就是Vue3,不管你是刚学完Vue2准备升级,还是纯新手直接上手Vue3,总会遇到几个卡壳半天、查半天文档才模模糊糊懂点皮毛的问题,今天就用日常踩坑复盘的语气,把自己和团队近一年用Vue3做电商后台、SaaS工具、小程序H5转译后踩过的“隐形雷”挖出来,再给一份落地可用的进阶路线。
新手入门必避:这些看起来没问题的写法,实则埋坑很深
刚摸Vue3时,很多人会把Vue2的习惯直接带过来,或者照着入门文档复制粘贴,等到上线前或者功能迭代才发现出bug,还找不到原因,下面这5个是最容易踩的新手雷,我团队一半的初级前端都犯过。
setup里还在写this?
刚转Vue3 Options转Composition的人,第一反应就是找不到$store、$router这些东西,下意识就去写this,结果要么报错undefined,要么拿到的this和Vue2完全不一样,是个Proxy代理的空壳或者上下文对象?其实Vue3 setup里完全不需要this,所有Vue2里挂载在原型上的API,现在都要单独导入:比如状态管理用pinia的话,直接引入useStore;路由用Vue Router4的话,引入useRouter和useRoute;refs用ref、reactive,computed和watch也是单独导入使用,刚开始可能觉得麻烦,每次写之前要先写一堆import,但用熟了会发现逻辑更集中,不用像Vue2那样在data、methods、computed之间跳来跳去找变量。
把ref和reactive混用到极致混乱
这是新手甚至有些中级前端都会犯的错:一会儿用ref定义对象,一会儿用reactive定义数组,一会儿又把ref.value拆开赋值给reactive的属性,结果响应式全乱套了,首先得明确ref和reactive的核心区别:ref是包装器,不管基本类型还是引用类型都能用,访问和修改都要加.value(模板里自动解包,但script标签顶层解构后不会,嵌套在reactive里会);reactive只能定义引用类型,访问和修改不需要加.value,但不能整体替换整个对象/数组,比如直接user = {}或者list = [1,2,3],响应式就会丢失,那具体怎么选?我的建议是:单个基本类型用ref,一组相关的引用类型用reactive(比如表单对象、用户信息对象),复杂点的组合(比如既有单个数字又有数组对象)可以都用ref,或者用reactive套个外层对象,比如const state = reactive({ count: 0, list: [], user: {} }),这样访问state.count、state.list就行,不用到处加.value,也不容易丢失响应式,不过要注意,如果用解构的方式取state里的属性,比如const { count, list } = state,那取出来的count和list就失去响应式了,这时候要配合toRefs使用,把state里的每个属性都转成ref,访问时要加.value,但响应式会保留。
完全不知道什么是“浅层响应式”“深层响应式”
Vue3默认的ref和reactive都是深层响应式,也就是不管对象/数组嵌套多少层,修改里面的属性都会触发视图更新,这在大部分场景下没问题,但如果遇到特别大的数据结构(比如十万级的JSON树、SaaS里的动态表单组件库配置),深层响应式的Proxy代理就会造成性能浪费,甚至页面卡顿,这时候就要用浅层响应式的API:shallowRef和shallowReactive,它们只会代理对象/数组的第一层,深层的修改不会触发视图更新,那什么时候用深层,什么时候用浅层?举个例子:如果你的JSON树只是用来展示,不需要实时修改深层节点,就用shallowReactive;如果动态表单组件库的配置是按需修改的,比如只改某一个输入框的label,就可以把配置拆成多个部分,需要实时响应的部分用reactive,不需要的用shallowReactive,还有两个辅助API:toRaw和markRaw,toRaw可以拿到ref/reactive包裹的原始对象,markRaw可以把某个对象标记为“永远不被响应式代理”,比如引入的第三方库实例、不需要更新的图片URL数组,都可以用markRaw包一下,避免不必要的性能开销。
v-for和v-if还放在同一个标签上?
这个坑Vue2里就有,但Vue3官方改了优先级,很多人没注意,踩得更深了,Vue2里v-for的优先级比v-if高,所以先循环再判断,不会有太大的性能问题,但代码不够优雅;Vue3里反过来了,v-if的优先级比v-if高?不对不对,查文档确认过,是v-if优先级比v-for高,所以如果放在同一个标签上,会先判断条件再循环,但这时候循环的变量(比如item、index)还没定义,直接报错!就算有的场景你把v-for变量放在v-if的父级里了,官方还是强烈建议不要放在一起,最好的做法是:先把要循环的数组用computed过滤一遍,再用v-for循环过滤后的数组,比如你有一个商品列表,要只显示库存大于0的商品,就不要写成
,而是写成computed(() => goods.filter(item => item.stock > 0)),然后用v-for循环这个computed值,这样不仅不会报错,性能还更好,因为computed有缓存,只有goods数组变化时才会重新计算过滤。生命周期钩子名字写错还不知道为什么不执行?
Vue3 Composition API里的生命周期钩子名字,基本都是在Vue2的前面加个on,比如mounted变成onMounted,created变成setup本身(因为setup是在组件创建之前执行的,没有created对应的单独钩子),beforeUnmount变成onBeforeUnmount,unmounted变成onUnmounted,很多人刚转过来会忘加on,或者名字写错(比如写成onMount、onBeforeUnMount),结果钩子永远不执行,还以为是自己的逻辑有问题,还有几个Composition API特有的钩子:onBeforeMount、onRenderTracked、onRenderTriggered,onBeforeMount对应Vue2的beforeMount,onRenderTracked会在响应式依赖被追踪时触发(调试用),onRenderTriggered会在响应式依赖变化导致视图更新时触发(调试用),调试这两个钩子的时候,可以配合Vue DevTools使用,能清楚看到哪些依赖触发了视图更新,方便优化性能。
中级进阶必懂:这5个技巧能让你的Vue3代码质量提升一个档次
避开了新手坑,接下来就要学一些中级技巧,让你的代码更简洁、更易维护、性能更好,下面这5个是我团队做了近10个Vue3项目总结出来的,落地性很强。
学会用自定义Hook复用逻辑
这是Composition API最核心的优势,也是Vue3和Vue2最大的区别之一,Vue2里复用逻辑主要靠mixin,但mixin有很多问题:比如命名冲突、来源不明确、逻辑分散,Vue3的自定义Hook完美解决了这些问题,它本质上就是一个普通的函数,里面可以使用所有Composition API,最后返回需要暴露给组件的变量和方法,举个例子:你有多个页面都需要实现“图片懒加载+加载失败显示占位图”的功能,就可以把这个逻辑封装成一个useImageLazyLoad的Hook,里面用ref定义图片的src、loading状态、error状态,用watchEffect监听图片元素进入视口的情况(可以用IntersectionObserver API,或者直接用VueUse里的useIntersectionObserver),然后返回src、loading、error、加载图片的方法,使用的时候,只需要在组件里引入这个Hook,调用它拿到返回值,然后绑定到图片标签上就行,多个页面用完全没问题,命名也不会冲突,来源也很明确(一看函数名就知道是干嘛的),推荐大家去用VueUse这个库,它是Vue3官方推荐的工具库,里面封装了几百个常用的自定义Hook,比如useMouse、useWindowSize、useStorage、useFetch等等,能大大提高开发效率。
合理使用Teleport传送门组件
这是Vue3新增的一个内置组件,非常实用,它的作用是把组件的内容传送到DOM树的任意位置,而不是放在组件的父元素下面,最常见的使用场景就是模态框、弹出层、提示框这类需要全屏覆盖的组件,比如你有一个嵌套在很深的div里的模态框,如果不用Teleport,模态框的父元素如果有overflow: hidden的属性,模态框就会被截断;如果用了Teleport,就可以把模态框传送到body标签下面,就不会被截断了,Teleport的使用也很简单:用
掌握Pinia状态管理的正确打开方式
Pinia是Vue3官方推荐的状态管理库,替代了Vuex,比Vuex更简洁、更易维护、类型支持更好,刚用Pinia时,很多人会把它当成Vuex的简化版,还是用actions、mutations那一套,但其实Pinia已经没有mutations了,只有state、getters、actions,state是响应式的,可以直接修改,不需要通过mutations;getters就是computed;actions可以是同步的也可以是异步的,不需要区分,那Pinia的正确打开方式是什么?要按模块划分store,比如用户信息模块、商品模块、购物车模块,每个模块单独创建一个store文件,用defineStore函数定义,state要用函数返回,避免多个组件共享同一个state对象;getters尽量用箭头函数或者函数形式,不要直接修改state;actions里可以调用其他actions,也可以调用其他store的actions,Pinia还支持插件、持久化存储(可以用pinia-plugin-persistedstate这个插件)、TypeScript类型支持,这些都可以根据项目需求使用,比如持久化存储,只需要安装pinia-plugin-persistedstate,然后在main.js里注册插件,在需要持久化的store里配置persist: true就行,state里的内容会自动保存到localStorage或者sessionStorage里,刷新页面后会自动恢复。
优化路由和组件加载,提升首屏渲染速度
现在的前端项目越来越大,首屏渲染速度直接影响用户体验和SEO排名,所以优化路由和组件加载是必须的,Vue3和Vue Router4配合,有几种常用的优化方式:第一种是路由懒加载,也就是只有当路由被访问时,才会加载对应的组件,写法是const Home = () => import('./views/Home.vue'),这样首屏只会加载App.vue和路由配置文件,不会加载其他页面的组件;第二种是路由预加载,也就是在首屏加载完成后,提前加载用户可能访问的下一个页面的组件,写法是在路由配置里加meta: { preload: true },然后在main.js里监听window.onload事件,遍历所有路由,如果meta.preload为true,就用import()预加载;第三种是组件的异步加载和Suspense组件配合,Suspense是Vue3新增的内置组件,用来处理异步组件的加载状态,比如可以显示一个加载动画,等异步组件加载完成后再显示内容,Suspense的使用也很简单:用
配合TypeScript使用,避免类型错误
Vue3从一开始就支持TypeScript,而且类型支持非常好,配合TypeScript使用可以大大提高代码质量,避免很多运行时的类型错误,刚开始用TypeScript和Vue3配合时,很多人会觉得麻烦,到处要写类型,但用熟了会发现真香,那具体怎么配合?创建项目时要选TypeScript模板,不管是用Vite还是Vue CLI都可以;script标签要加lang="ts";ref、reactive、computed这些API都可以加泛型,比如const count = ref
给转Vue3开发者的3个小建议
说了这么多踩坑和技巧,最后给大家3个实用的小建议:第一,不要一开始就把整个Vue2项目迁移到Vue3,先迁移一个小的模块或者页面,熟悉一下Composition API和其他新特性,再逐步迁移整个项目;第二,多去看Vue3的官方文档和Vue Mastery的课程,官方文档写得非常详细,Vue Mastery的课程是Vue3核心团队成员讲的,能学到很多底层原理;第三,多去用VueUse和Vite这些工具库和构建工具,能大大提高开发效率和项目性能。
Vue3是一个非常优秀的前端框架,不管是新手还是老Vue2开发者,都值得去学习和使用,只要避开了这些隐形雷,掌握了这些中级技巧,相信你很快就能写出高质量的Vue3代码。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


