ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Vue2升级Vue3实战:从工程配置到响应式改造的踩坑全记录

Vue2升级Vue3实战:从工程配置到响应式改造的踩坑全记录 把一套跑了三年多的vue2项目整体改造到vue3这个念头我动过好几次也预想过会踩不少坑但真正动手之后才发现坑比我预想的还要多。项目本身不算大一个内部后台管理系统vue2.6 element-ui vuex vue-router3 axios加上几张可视化大屏还有一堆自己封装的历史组件。说实话vue2项目改造vue3这个话题网上教程一抓一大把但真正走到生产环境迁移这一步光看文档是不够的。这篇文章不打算从头教你vue3语法而是把我在这次改造中遇到的典型问题、解决办法和排查思路记录下来正好也想给那些正准备动手或者已经卡在半路上的同学一些参考。全文涉及工具链切换、响应式差异、组件库适配、路由状态管理迁移最后附上常见报错速查表整个过程可复现。1. 改造前的评估和路线选择1.1 先盘清楚项目里到底有什么很多人拿到迁移任务第一反应就是新建一个vue3项目然后把src目录整个复制过去。这个做法我只能说勇气可嘉结果通常是在几百个报错面前怀疑人生。我建议第一步先把项目“家底”盘清楚再动手改。打开package.json我会把所有依赖拉一个清单。你至少要确认这些信息Vue版本是2.6还是2.7路由是vue-router3还是更低状态管理用的是vuex还是别的UI库是element-ui、ant-design-vue还是自研组件有哪些第三方库已经停止维护。我的老项目里就有两个只有vue2版本的老插件是当初几个人临时封装的小工具早就没人维护了这类依赖必须提前识别出来。光看依赖还不够还要在代码层面做一次全局搜索。我强烈建议跑这几个命令先把“雷”找出来grep -r Vue.prototype src grep -r this.\$listeners src grep -r this.\$children src grep -r filters src为什么查这几个因为Vue3里全局属性挂载方式变了$listeners合并进了$attrs$children被移除了过滤器整个没了。这些在老项目里出现频率其实不低先把它们统计出来你就知道代码改造的工作量大概是多少。这一步做完你心里基本就有数了哪些目录可以原封不动搬过去哪些目录注定要大改。1.2 迁移路线一步到位还是渐进式市面上关于vue2迁移vue3的路线大致分两种。第一种是渐进式迁移。利用vue/composition-api在vue2项目里先熟悉组合式API的写法或者借助vue-codemod这类工具做部分自动转换更复杂的还会考虑用微前端框架把新旧两套项目在同一套外壳下共存让vue2的老模块和vue3的新模块并行运行然后逐个替换。这个路线对大项目友好但缺点也很明显两套体系共存意味着双份依赖、双份构建配置成本和复杂度并不低而且如果团队本身对微前端不熟反而是给自己挖了一个更大的坑。第二种就是全量重建。创建新的viteproject把src代码搬过来然后一个坑一个坑地填。我给自己的判断标准是如果项目模块耦合度不高、页面也就几十个、团队成员少全量重建更干脆如果项目庞大、模块之间依赖复杂、生产环境不允许长时间停摆那就老老实实走渐进式。我这边选的是全量重建。内部系统没有外部用户压力而且老项目里不少历史组件本来就是从外面复制来改的代码风格不统一正好借这个机会清理一遍。我的策略很简单先跑起来再替换最后优化不要想着一次性把所有问题都处理完。1.3 依赖版本兼容性规划迁移前先把依赖版本对应关系搞清楚能省掉后面一大堆碰运气的操作。我整理了一张表建议大家也照着做一份贴在工位边上旧依赖Vue2 版本Vue3 方案注意事项vue2.63.x必换vue-router3.x4.xAPI变化较大通配符写法必须改vuex3.x4.x或piniavuex4只是兼容版更推荐piniaelement-ui2.15.xelement-plus不是完全兼容改了非常多APIaxios无版本约束无需更换但封装层要检查echarts5.x5.x初始化方式不变注意销毁vuedraggable2.xvuedraggable4名字一样但内部改了不少vue-virtual-scroller1.x2.xVue3必须用2.x这里要特别注意一点如果你们的项目其实是用HBuilderX开发的uni-app那迁移路径又不一样。uni-app的vue2工程可以通过在manifest.json里切换vue3编译版本官方有一套单独的处理方案不能直接照搬普通web项目的改法。热词里提到的“uniapp vue2转vue3方法”就是指这个场景。另外Node环境也得提前确认。组里有同事的电脑还是win7这就很头疼因为新版Vite对Node版本要求高win7上跑新版本Node基本是不可能的事。实际经验告诉我vite5要求Node18以上而win7能稳定支持的Node版本也就到16那一代所以如果团队里有win7开发机要么强制升系统要么锁定Vite版本否则项目根本起不来。2. 工程化改造从 webpack 到 Vite 的阵痛2.1 新项目初始化和基础配置我这边用的是Vite因为Vue3官方推荐、启动速度快而且很多脚手架模板已经默认就是vite。初始化命令很简单npm create vitelatest my-vue3-project -- --template vue如果你的项目需要TypeScript把模板参数换成vue-ts就行。这个命令会生成一个干净的vite vue3项目结构然后把老项目的src目录整体拷过来剩下的工作就是消灭报错。这里我必须强调一个容易忽略的步骤新项目的依赖不是照抄老项目而是按新版本重新安装。我的做法是先把核心依赖装好再根据报错逐个补。核心依赖安装命令npm install vue3 vue-router4 pinia element-plus axios npm install -D vite vitejs/plugin-vue sass依赖装完最先要改的就是vite.config.js。老项目里 vue.config.js 的 devServer、publicPath、alias配置要全部搬到新配置里下面这份配置我看着很顺手import { defineConfig } from vite import vue from vitejs/plugin-vue import path from path export default defineConfig({ plugins: [vue()], resolve: { alias: { : path.resolve(__dirname, src) }, extensions: [.mjs, .js, .ts, .jsx, .tsx, .json, .vue] }, server: { host: 0.0.0.0, port: 8080, proxy: { /api: { target: http://192.168.1.100:3000, changeOrigin: true, rewrite: (p) p.replace(/^\/api/, ) } } }, base: ./ })配置里的每一项都有讲究。alias不配置老的/components这种导入路径全部会失效extensions不提前配好某些第三方包导入时不带扩展名会直接报模块找不到base: ./是为了部署到子目录或者文件协议下也能正常加载静态资源这个问题在vue2项目里通常用publicPath解决vue3里别忘了一起配置。2.2 环境变量与代理设置差异环境变量这块看起来小实际踩坑的人特别多。Vue2的webpack项目里开发环境变量都是process.env.VUE_APP_XXX这种写法到了vite里变成了import.meta.env.VITE_XXX前缀还从VUE_APP变成了VITE所有引用环境变量的地方都要跟着改。我们老项目里API地址是这么写的// vue2 写法 const baseURL process.env.VUE_APP_API_BASE || /apivue3项目里要改成// vue3 写法 const baseURL import.meta.env.VITE_API_BASE || /api如果项目里引用的地方多我当时的做法是写一个公共的config文件把环境变量统一收口后面万一要再改只需要动一个文件。注意.env.development、.env.production这些文件名虽然不变但文件里变量的前缀一定要换成VITE_。代理配置也是重灾区。vue.config.js里的devServer.proxy要改成vite的server.proxy格式大体相似但有些细节不同。比如vue2的webpack代理可以用pathRewritevite里要写成rewrite这个字段名改了我整整半天才查出来。还有一个真实遇到的现象就是热词里那条“运行后 network: unavailable以前可以突然就不行了”。这种情况在vite开发环境里出现过一次。排查下来发现局域网IP地址变了而之前浏览器标签页里打开的地址是旧IP再加上系统防火墙弹窗没注意到新端口被拦住了。处理方式其实很朴实命令行里看vite实际监听的地址用新地址替换浏览器里的旧地址再不行就检查防火墙和端口占用用lsof -i:8080这种命令看看端口是不是被别的进程占了。2.3 构建兼容性与特殊场景Vite底层用的是esbuild对CommonJS模块和某些老式的构建工具链兼容性不如webpack那么“宽容”。我遇到的一个典型问题是老项目里引入了一个只在vue2环境下正常工作的库它是CommonJS模块vite在预构建依赖时直接报警告页面跑起来白屏。解决办法是去vite配置里显式告诉它哪些依赖需要特殊处理optimizeDeps: { include: [vueuse/core, some-old-lib], exclude: [] }如果项目里有全局的SCSS变量还要在vite里配一下css预处理器的全局注入。老项目用的是webpack的sass-loader加additionalDatavite的配置类似css: { preprocessorOptions: { scss: { additionalData: use /styles/variables.scss as *; } } }这里注意新版sass语法推荐用use而不是老式的import否则会有大面积警告。项目里如果用了JSX需要额外装vitejs/plugin-vue-jsx并在plugins数组里加进去否则.tsx/.jsx文件里的组件根本编译不过。还有一点如果老项目里用了__dirname、process.env这类Node环境变量在浏览器端是会直接报错的因为这些变量在浏览器运行环境里根本不存在。这种代码通常出现在vite.config或某个配置文件里迁移时留意一下。3. 响应式与组件 API代码层面的全面改写3.1 data、computed、watch 的迁移套路这是整个迁移过程中最磨人的部分也是热词里“vue2的data和return”“vue3 computed”这些搜索词被频繁检索的原因。很多人学vue2时第一个接触的写法就是data() { return {...} }到了vue3反而有点懵因为组合式API里你不再写data函数而是直接用ref和reactive。我建议对于老项目一开始不要强求全部改成script setupOptions API在Vue3里仍然能用很多老代码原封不动也能跑。但如果你的项目想真正吃上Vue3的红利迟早要一步步往setup方向靠。我用一组对照来说明最常见的迁移方式。Vue2写法export default { data() { return { list: [], total: 0, keyword: } }, computed: { filteredList() { return this.list.filter(item item.name.includes(this.keyword)) } }, watch: { keyword(newVal, oldVal) { this.fetchList() } }, methods: { fetchList() { this.$http.get(/list, { params: { keyword: this.keyword } }) .then(res { this.list res.data.list this.total res.data.total }) } } }对应的Vue3组合式API写法script setup import { ref, computed, watch } from vue const list ref([]) const total ref(0) const keyword ref() const filteredList computed(() list.value.filter(item item.name.includes(keyword.value))) watch(keyword, (newVal, oldVal) { fetchList() }) async function fetchList() { const res await http.get(/list, { params: { keyword: keyword.value } }) list.value res.data.list total.value res.data.total } /script这段代码看着简单里面藏着好几个新手必踩的坑。第一ref创建的数据在JavaScript里要用.value访问模板里可以自动解包。第二computed返回值本身就是一个ref模板里直接写filteredList但script里要写filteredList.value。第三不要把reactive对象直接解构解构出来的属性会失去响应式正确做法是用toRefs包裹后再解构。还有一个关于数组和对象的响应式差异很多人没意识到。Vue2里给对象新增属性必须用this.$set()否则界面不会更新这个问题在vue3里已经不存在了因为代理对象能拦截到新增属性。但反过来如果你习惯直接通过索引改数组比如this.list[0] xxx这在vue2里不响应在vue3里是响应的行为变了。这种变化会让老代码的“绕路写法”变慢但没必要专门去改只要保证行为一致就行。3.2 全局属性、过滤器、事件总线老项目里经常能见到Vue.prototype.$http axios这种全局挂载然后组件里到处用this.$http.get(...)。Vue3里这条路走不通了必须改成在应用实例上挂载// main.js import { createApp } from vue import App from ./App.vue const app createApp(App) app.config.globalProperties.$http http app.mount(#app)这样组件里依然能通过this.$http访问但如果你用的是script setup组件里其实拿不到this更推荐的做法是把axios实例单独封装成一个文件直接用 import 引入。过滤器是另一个容易踩坑的点。Vue3彻底移除了过滤器功能老项目里模板上写的{{ price | formatPrice }}会直接报错。我的处理方案是全部改成方法调用或者computed例如template span{{ formatPrice(price) }}/span /template script setup function formatPrice(value) { return ¥ value.toFixed(2) } /script事件总线也不用折腾了。Vue3里$on、$off、$once这些都移除了老项目里那种new Vue()当事件中心的做法全部失效。我用的替代方案是引入mitt只有200多字节API长得又和事件总线很像import mitt from mitt const emitter mitt() export default emitter一个地方emitter.emit(refresh)另一个地方emitter.on(refresh, handler)迁移成本极低。另外$children和$listeners也变了。$children直接移除组件间通信建议用ref或状态管理$listeners被合并进了$attrs所以老代码里v-on$listeners要改成v-bind$attrs这个改法我最初也摸索了好一阵子。3.3 模板指令和生命周期差异Vue3对模板指令的优先级做了一个调整Vue2里同一个元素上同时写v-if和v-for时v-for优先Vue3里反过来v-if优先。老项目里如果写过这种“危险”代码迁移后会出现渲染结果和原来不一样的情况而且很难排查。我遇到的一个列表过滤问题最终就定位到是这里的优先级变化。解决办法是加一层template包裹或者把过滤逻辑提前移到computed里不要在模板里同时用v-if和v-for。生命周期也改了名字beforeDestroy变成beforeUnmountdestroyed变成unmounted。这个改名很直白但老代码里只要用了旧名字控制台就会提示“Invalid lifecycle hook”不会直接报错导致白屏容易被忽视。如果你用的是组合式API对应的是onBeforeUnmount和onUnmounted跟onMounted配套使用。比如echarts图表实例的销毁就要在onBeforeUnmount里处理不然页面路由切换次数多了浏览器内存会一路飙升。关于ref的用法也要提醒一句Vue3里在v-for中使用ref的行为不像vue2那样自动收集成数组了具体行为在不同版本之间还有变化。我的建议是别依赖这种“自动收数组”的写法老老实实用函数式reftemplate div v-foritem in list :refel setItemRef(el)/div /template script export default { methods: { setItemRef(el) { // 手动处理 el } } } /script热词里提到的defineComponent创建组件也是在改用TypeScript或者写通用组件时绕不开的东西。用script setup时可以不写但如果你想给组件显式声明name或者在选项式写法里获得更好的类型推导就建议用defineComponent包一层import { defineComponent } from vue export default defineComponent({ name: HelloWorld, props: { msg: { type: String, default: } } })4. 组件库与第三方生态适配4.1 Element UI 到 Element Plus 的适配点后台管理系统基本绕不开Element这套组件库。老项目用的是element-ui新项目要用element-plus。网上很多人以为就是把包名换一下实际差异大得让人头秃。先要把原来的element-ui替换掉然后全局引入npm uninstall element-ui npm install element-plusmain.js里改成import ElementPlus from element-plus import element-plus/dist/index.css const app createApp(App) app.use(ElementPlus)然后噩梦就开始了。简单列几个我踩过的高频差异el-button的size属性vue2里是medium、small、minivue3的element-plus改成了large、default、small老代码里写sizemedium的按钮全部会变回默认大小。el-dialog控制显隐的visible属性改成了visible和v-model都能用但官方推荐v-model。更麻烦的是老代码里:visible.syncdialogVisible这种写法vue3里.sync修饰符被移除了要统一改成v-model或者v-model:visible。el-table的插槽写法变化很大老代码里常见的slotscope这种具名插槽在vue3里行不通了要改成#defaultscope。表单校验的底层库从async-validator换成了新版虽然API大体兼容但自定义校验器里的callback用法变得不那么强制了如果你原来在validator里不管成功失败都要调callback新版本里可能因为混用promise导致表单永远校验不通过。Popconfirm气泡确认框这个案例特别能说明问题。热词里有一条“vue2 element ui popconfirm 气泡确认框 增加一个输入框”我第一次迁移时也碰到了类似需求。现实是Popconfirm这个组件本身定位就是“确认操作”对自定义内容的支持一直不友好官方文档里插槽有限你想在确认框里塞一个输入框老办法是各种hack。我的建议是不要死磕它直接换成el-popover自定义内容自己做确认逻辑反而更清晰。在element-plus里弹层的自定义内容能力比popconfirm强太多迁移也更省心。4.2 按需引入、主题样式与深层选择器Element Plus除了全量引入还支持按需引入。如果你对打包体积有要求可以配合unplugin-auto-import和unplugin-vue-components这两个插件在vite.config.js里配置import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers export default defineConfig({ plugins: [ vue(), AutoImport({ resolvers: [ElementPlusResolver()] }), Components({ resolvers: [ElementPlusResolver()] }) ] })内部系统如果没有体积焦虑就别折腾按需引入了全量引入最省事这是真心话。样式问题要重点说。vue2时代我们习惯在scoped样式里写::v-deep来穿透子组件到vue3里这个写法直接在编译期报错正确写法是:deep()/* vue2 写法已经失效 */ ::v-deep .el-tabs__item.is-active { color: #409eff; } /* vue3 写法 */ :deep(.el-tabs__item.is-active) { color: #409eff; }热词里那条“vue3修改tabs标签页样式”就是这个问题。不止tabs表格、弹窗、表单组件的样式覆盖全部要跟着改这个工作量说实话不小。4.3 大屏、图表、拖拽懒加载和iframe老项目里有几张可视化大屏里面大量使用echarts和el-table。echarts组件的初始化方式在vue3里变化不大但要注意用ref而不是id因为vue3的响应式环境下用document.getElementById拿DOM节点容易在组件还没渲染完时取不到。推荐写法import * as echarts from echarts const chartRef ref(null) let chart null onMounted(() { chart echarts.init(chartRef.value) chart.setOption({ // 配置项 }) }) onBeforeUnmount(() { if (chart) { chart.dispose() chart null } })大屏表格还有个老问题路由切换后容器宽度从隐藏变成显示echarts拿不到正确的宽度渲染出来是0宽。vue2里要自己监听resizevue3里依然要监听。建议在tab切换或者v-if控制显隐的地方手动调用chart.resize()。列表拖曳排序、列表懒加载这些功能如果老项目用的是vuedraggable2.x和vue-virtual-scroller1.xvue3里要分别换成vuedraggable4和vue-virtual-scroller2。包名不变但内部API有差异装的版本不对的话页面会直接白屏。这里给一个用sortablejs给element-plus表格行做拖曳排序的示例思路import Sortable from sortablejs const tableRef ref(null) function initSortable() { const tbody tableRef.value.$el.querySelector(.el-table__body-wrapper tbody) Sortable.create(tbody, { handle: .drag-handle, onEnd({ oldIndex, newIndex }) { const item list.splice(oldIndex, 1)[0] list.splice(newIndex, 0, item) } }) }热词里还有一条“vue3嵌套iframe没办法触发iframe外层div的点击事件”。这个问题我在一个嵌入报表的页面里遇到过。原因很简单iframe是一个独立的可交互区域点击事件会被iframe自身吞掉不会冒泡到外层div。如果你只是想在用户点击iframe区域时做一个埋点或者跳转最朴素的方案是在iframe上面覆盖一个透明div来承接点击等真正需要操作iframe内容时再把透明层移除。另一个思路是用document.activeElement判断焦点是否进入iframe再配合window.blur事件来感知切换。这些都是成熟做法但记得测试边界情况。地图、MQTT、OnlyOffice这类第三方库本身跟Vue版本关系不大但如果老代码把它们挂到了Vue.prototype或者window上迁移时就免不了要重新封装。比如mapboxgl如果你在setup里初始化地图一定要确保DOM已经渲染完成否则容器拿不到地图直接黑屏。5. 路由和状态管理迁移5.1 Vue Router 4 API 升级要点vue-router从3.x升到4.x写法变化一目了然。老项目里是import VueRouter from vue-router Vue.use(VueRouter) const router new VueRouter({ mode: history, routes })新项目里要这样写import { createRouter, createWebHistory } from vue-router const router createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes }) export default router几个必改点mode: history变成createWebHistory()mode: hash变成createWebHashHistory()。通配符路由path: *改成path: /:pathMatch(.*)*这个不写对404页面永远匹配不上。router.addRoutes()方法没了只能一个一个用router.addRoute()。路由守卫的写法虽然兼容但官方更推荐直接返回路由对象。比如router.beforeEach((to, from) { if (to.meta.requiresAuth !isLogin()) { return { path: /login } } return true })老代码里大量使用next()回调的写法在vue-router4里虽然还能用但新版更推荐用上面的返回值风格迁移时顺手改掉以后维护也轻松。5.2 用 Pinia 还是 Vuex4状态管理这块vuex4是官方为了兼容vue3出的版本能用但谈不上好用。我更推荐Pinia这也是目前vue3生态的主流选择。不是赶时髦是Pinia真的太省心了没有mutations直接改state天然支持setup和Options两种风格TypeScript支持也比vuex好得多。安装很简单npm install piniamain.js里注册import { createPinia } from pinia const pinia createPinia() app.use(pinia)定义store长这样import { defineStore } from pinia export const useUserStore defineStore(user, { state: () ({ token: localStorage.getItem(token) || }), getters: { isLogin: (state) !!state.token }, actions: { setToken(token) { this.token token } } })在组件里使用的时候最容易踩的坑是解构import { useUserStore } from /stores/user import { storeToRefs } from pinia const userStore useUserStore() // 直接解构会丢失响应式 const { token } userStore // 正确做法用storeToRefs包裹 const { token } storeToRefs(userStore)这个坑跟响应式系统那节说过的reactive解构问题如出一辙本质就是千万别把响应式对象的属性“抠出来”单独用抠出来就变成普通值了。热词里“vue3 pinia”被搜那么多次估计不少人就是卡在这里。5.3 微前端场景下的全局变量处理如果老项目接入了qiankun微前端迁移到vue3后还有一个额外的坑老代码里可能大量使用window.xxx来传递公共数据比如用户信息、权限列表。这种做法在微前端环境里很容易造成子应用之间的数据污染。我的建议是在新架构里通过qiankun的props机制显式传递公共数据而不是让子应用自己挂在window上。主应用注册子应用时把共享接口传下去子应用在生命周期mount里接收并使用。这块改造虽然不复杂但涉及主应用和子应用两边的代码测试时一定要把“单独启动子应用”和“由主应用启动子应用”两种模式都跑一遍很多问题只有在第二种模式下才暴露。6. 常见报错与排查技巧实录6.1 高频报错速查表迁移过程中我把遇到的高频报错整理成了一张表贴在新项目根目录下。每次报错先查表比自己重新摸索快得多报错信息 / 现象原因解决办法Cannot read property $message of undefined全局属性没有挂载或组件里用this.$message但实例上不存在main.js里app.config.globalProperties.$message ElMessageFailed to resolve component: el-xxxElement Plus组件没有被注册检查是否app.use(ElementPlus)按需引入的话检查 resolver 配置Property “xxx” was accessed during render but is not definedsetup返回值里没有暴露该变量或者拼写错误script setup下检查变量是否定义并导出Cannot use import statement outside a module某个配置文件或依赖被当作浏览器代码执行检查package.json的 type 字段vite项目一般设 type: module或调 optimizeDeps[Vue Router warn] No match found for location with path “/xxx”路由通配符写法不对URL匹配不了把通配路由改成/:pathMatch(.*)*$store is undefined项目用了vuex或pinia但注册没执行检查 main.js 里是否app.use(pinia)Invalid lifecycle hook: beforeDestroy生命周期名称未改beforeDestroy 改成 beforeUnmountdestroyed 改成 unmountedFailed to resolve import …… does not provide an export named xxx某个CommonJS模块被按ESM方式导入改用默认导入或配optimizeDeps.include这张表不是全量但覆盖了我实际碰到的最常见的80%问题。遇到没列出来的我的排查顺序是先看控制台完整报错堆栈定位是哪个模块、哪一行再确认是不是依赖版本问题最后才是业务代码逻辑。6.2 几个冷门但特别耽误时间的问题有些问题不是高频但一旦碰到能卡一整天。我挑几个有代表性的说说。第一个是热词里的“若依vue3 ts报错”。若依这类老牌后台框架的代码风格是从vue2时期延续下来的它的vue3版本虽然官方已经放出来了但你要自己从vue2版本升的话大概率会遇到一堆TypeScript类型报错。我遇到最多的就是第三方库没有类型定义比如某些老模块declare module xxx缺失或者router里meta字段类型不对。这类问题本质上不是vue3的问题而是你在给一个没有类型系统的老项目硬补TS类型。我的建议是迁移初期先关闭类型检查等功能稳定了再一个个补类型不然迁移和补类型两件事叠在一起进度会非常难看。第二个是“vue2项目运行项目时network: unavailable”这种怪异现象。这类问题一般是开发环境层面的但迁移后更容易出现因为vite的监听地址和webpack不一样而且HBuilderX这类工具的缓存机制也可能在作怪。解决办法是重启开发服务器、清缓存、检查监听的是127.0.0.1还是局域网IP。如果项目本来是用HBuilderX开发的uni-app在迁移到vue3编译模式后遇到这种问题先确认manifest.json里的vue版本配置有没有生效再检查HBuilderX版本是否足够新。uni-app的vue3支持在不同版本的HBuilderX里差异比较大。第三个是热词里提到的“vue3项目在edge浏览器中有时候无法关闭浏览器右上角的最小化按钮”。这个问题乍一看很玄幻跟vue3代码关系也不大。实际排查发现这类浏览器行为异常多半是GPU渲染、浏览器扩展或者系统层面的干扰跟框架关系不大。我的处理建议是先让别人用其他浏览器试试如果只有Edge出问题优先考虑强制刷新、禁用扩展、切换渲染模式而不是在代码里瞎找。迁移过程中遇到这种“怎么排查都跟框架无关”的问题别死磕先换环境验证。第四个是关于uni-app页面生命周期。如果你的项目是uni-appvue2转vue3时需要注意onLoad、onShow这些是uni-app页面生命周期不是vue的生命周期在vue3里依然要用import { onShow, onLoad } from dcloudio/uni-app千万不要把项目的onShow误以为要改成onMounted这两个不是一个概念。uni-app的页面生命周期和vue组件生命周期是两套东西很多人迁移时被这个绕晕。热词里能搜到“vue3 app.vue onshow options”说明困惑的人不少。我碰到的一个例子是在uni-app的vue2项目里App.vue的onLaunch里初始化登录态迁移到vue3时有人把它改成了onMounted结果登录态初始化时机全乱了。正确的做法是uni-app项目依然用onLaunch这些框架特有的生命周期不会因为底层是vue3就消失。6.3 排查思路建议最后分享一点排查经验。迁移过程说白了就是“让报错越来越少”的过程但这有个前置条件你要保证每一轮改动都是一个可运行的状态而不是攒一堆报错再一次性处理。我的工作流是先把项目启动起来哪怕页面是白的只要服务不崩就在控制台一个一个解决报错每解决一个就提交一次git每完成一个模块就重新跑一遍核心流程。这个习惯在迁移“大而全”的后台系统时特别重要因为很多问题是连锁的A模块的错可能到了B模块才暴露出来。能回退到稳定版本就是给自己留后路。另一个建议是优先处理全局性的改动。比如main.js的创建方式、路由初始化、状态管理注册、UI库引入这些是一个项目的地基地基不稳定业务组件里的报错会像野草一样长不完。把这些基础配置一次改对后面业务代码的报错才会收敛。最后一个容易被忽视的点注意看控制台警告不要只盯着报错。vue3和element-plus会在警告里提示很多即将废弃的用法和潜在问题这些警告早期不管等业务代码量上来之后会变成另一座“技术债”山。我个人实际体会是vue2项目改造vue3这件事真正困难的不是某一条语法或某个API而是你需要在迁移的同时重新理解一遍项目的底层设计。很多老代码在vue2的年代是workaround出来的比如用this.$set处理响应式、用事件总线做跨组件通信、用slotscope写表格列这些绕路的写法到了vue3里反而可以变得更简单。如果只是机械地把老代码翻译成新语法那迁移的价值就少了一半。最后再补一个实用小技巧改造过程中每完成一个重要模块的迁移就在本地把打包后的生产构建跑一遍别只在开发环境看效果。vite开发环境和生产构建不做某些严格校验很多问题在开发环境不报错一打包就原形毕露。我这次就在nginx部署那一步栽了个跟头开发环境一切正常构建完部署到服务器上一刷新子路由就是404最后检查下来是vue-router的history模式没配nginx的 try_files 回退规则需要在server配置里加上try_files $uri $uri/ /index.html;。这种部署层面的问题单靠开发环境永远测不出来早点打包、早部署、多刷新才能早点暴露出来。
返回列表