ARTICLE DETAIL

资讯详情

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

Vue 2老项目实操指南:环境搭建、动态路由与打包部署避坑

Vue 2老项目实操指南:环境搭建、动态路由与打包部署避坑 前两天一个朋友把个老项目丢给我说帮我跑起来公司急着演示。我打开package.json一看——vue: ^2.6.14node-sass还老老实实躺在依赖列表里瞬间有种穿越回2019年的感觉。这种经历基本就是现在还在搜vue.2的人每天面对的现实Vue 2 官方维护已经终止但线上有大量项目还在跑新人也还在靠 Vue 2 的教程入门面试题里问的也还是 Vue 2 那套响应式原理。这篇文章我就把这个过程中踩过的坑、验证过的方案完整捋一遍从环境搭建、路由权限、第三方集成到打包含进 SpringBoot、面试备考给还在跟 Vue 2 缠斗的人一份能直接照着做的实操清单。1. 接手一个没人愿意碰的老项目Vue 2的现实处境1.1 为什么到了现在还会遇到 Vue 2先说个客观事实Vue 2 在 2023 年 12 月 31 日之后官方不再提供安全更新和维护。但如果你以为这就意味着全世界都切到 Vue 3 了那就太天真了。我这两年接到的需求里至少一半是存量 Vue 2 项目的迭代有的是公司技术栈老、团队没精力迁移有的是项目太久没人动新人不熟悉 Vue 3 不敢动还有不少外包项目合同就是按 Vue 2 定的改架构是要出人命的。搜vue.2这个关键词的人基本可以分成三类。第一类是刚开始学前端的小白看教程发现很多教学视频都基于 Vue 2于是顺着查环境配置第二类是手里压着老项目的开发需要快速解决部署、路由、依赖安装这类问题第三类是准备面试的人因为 Vue 2 相关的面试题仍然大量存在于题库里。这篇文章主要照顾的就是后两类第一类人看完也能对整个项目生命周期有完整的认知。1.2 一个 Vue 2 老项目的典型特征接手老项目第一件事不是看代码是看package.json。Vue 2 项目的依赖结构非常有时代感{ vue: ^2.6.14, vue-router: ^3.5.3, vuex: ^3.6.2, axios: ^0.24.0, element-ui: ^2.15.6, node-sass: ^4.14.1, sass-loader: ^7.3.1, webpack: ^4.46.0 }注意看几个关键点Vue Router 一定是 3.xVuex 一定是 3.x这两个库的 4.x 版本是专门给 Vue 3 用的装错了直接报错。UI 框架方面Element UI 基本是标配但也有老项目用 iView 或者自研组件库。构建工具大概率是 Webpack 4配 Vue CLI 4 或 3。这个组合决定了你在环境搭建阶段的核心任务不是装最新版而是让这些老版本在当前的系统上还能跑起来。下面就把整个过程拆开细讲。2. 重新拉环境Node版本、CLI与依赖管理的互相妥协2.1 环境配置第一步锁死Node版本Vue 2 项目对环境最敏感的部分是依赖编译。很多老项目的依赖尤其是原生模块是针对特定 Node 版本编译的Node 版本太新会直接编译不过。我习惯用 nvmNode 版本管理器来管理不同项目切换 Node 版本非常方便不需要反复折腾系统环境。以我这次接手的老项目为例测试下来 Node 14 是最稳妥的选择。不要纠结为什么不用最新版 Node 20、22——node-sass 4.x 在 Node 14 上能正常编译在更高的 Node 版本上大概率会报错Webpack 4 在 Node 17 上会直接跑出ERR_OSSL_EVP_UNSUPPORTED这个经典错误原因是 OpenSSL 3.0 不再支持旧版加密算法。如果你必须用新 Node可以给打包命令加上NODE_OPTIONS--openssl-legacy-provider临时跳过但这属于治标不治本换回老版本才是正路。# 安装并使用 Node 14 nvm install 14.21.3 nvm use 14.21.3 node -v选定版本后最好在项目根目录加一个.nvmrc文件内容写14.21.3这样团队成员一进入目录执行nvm use就能自动切到正确版本能省掉大量我这能跑你怎么跑不了的扯皮。2.2 node-sass 已死sass 当立一次痛苦的依赖迁移Vue 2 老项目里最让人头疼的依赖就是 node-sass。它是一个原生模块安装的时候需要从 GitHub 下载二进制文件而它跟 Node 版本是严格绑定的——Node 版本一换它就要重新编译网络稍有不顺畅就卡死报错信息还会把新手吓个半死。我这次的方案是彻底弃用 node-sass迁移到sass也就是 dart-sass。二者的 API 基本兼容但注意两个破坏性差异。第一/deep/选择器在 dart-sass 中不推荐使用应该用::v-deepVue 2 会把它编译成后代选择器第二sass 的新版本里除法运算符/被废弃需要改成math.div()。如果你的项目里大量使用/做计算迁移时要用math.div替换。# 卸载 node-sass npm uninstall node-sass # 安装 dart-sass 与适配的 sass-loader npm install -D sass1.32.13 sass-loader10.1.1这里特别注意sass-loader的版本不能乱选。sass-loader 13 以上是为 Webpack 5 设计的老项目的 Webpack 4 必须用 10.x装高了会报this.getOptions is not a function。这类版本间的互相妥协就是老项目环境搭建中最常见的问题我在下文汇总了一张常用的依赖版本对照表。依赖推荐版本说明node14.21.3与老依赖兼容性最好vue-cli4.5.15基于 Webpack 4稳定不折腾sass1.32.13旧版 API样式兼容性佳sass-loader10.1.1适配 Webpack 4jsdom16.x老测试套件兼容性2.3 npm依赖安装的加速与锁版本策略换依赖的时候小团队直接删掉node_modules和package-lock.json重新安装是可行的但大项目千万不要这么干。锁文件里固化了每一个传递依赖的精确版本删了它很多隐式依赖就放飞自我了装出来一套全新的依赖树各种诡异报错接踵而至。正确流程是先备份旧的锁文件然后移除node_modules手动把要替换的依赖从package.json里调整好最后执行npm install。如果你的网络环境装 npm 包很慢可以在项目根目录放一个.npmrc内容如下registryhttps://registry.npmmirror.com sass_binary_sitehttps://npmmirror.com/mirrors/node-sass phantomjs_cdnurlhttps://npmmirror.com/mirrors/phantomjs这只影响 npm 的下载源不影响任何代码逻辑。sass_binary_site这种配置原先是给 node-sass 用的虽然我们已经弃用它但留着没坏处万一其他依赖还在下二进制文件呢。装完跑一次npm run serve如果还报跟 TypeScript 相关的错比如热搜里那个failed to load tsconfig vue/tsconfig/tsconfig.web.json这说明项目用的是 Vue CLI 的 TS 模板且模板版本与vue/tsconfig包版本对不上。解决办法是把vue/tsconfig降到与 Vue CLI 4 匹配的 0.1.3 版本或者在tsconfig.json里直接去掉extends配置改成手写编译选项。这类报错几乎都是版本错配不用动业务代码。3. 路由体系的重构从静态路由到动态权限路由3.1 Vue Router 3 的基础配置与惯用写法环境跑通之后真正要动脑子的就是路由了。Vue 2 配的是 Vue Router 3路由配置文件src/router/index.js通常是这个套路import Vue from vue import VueRouter from vue-router Vue.use(VueRouter) const routes [ { path: /, redirect: /dashboard, }, { path: /login, name: Login, component: () import(/views/Login.vue), }, { path: /dashboard, name: Dashboard, component: () import(/views/Dashboard.vue), meta: { title: 工作台, requiresAuth: true }, }, ] const router new VueRouter({ routes, mode: history, scrollBehavior(to, from, savedPosition) { return savedPosition || { x: 0, y: 0 } }, }) router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }) } else { next() } }) export default router这里有两个细节要提醒。第一mode: history会让 URL 更干净但部署到 SpringBoot 这类后端服务时必须配转发规则否则一刷新就 404这个坑我在第五节单独讲。第二路由懒加载用箭头函数配合动态import()这是老项目也能用的性能优化手段至少让首屏体积小三分之一。3.2 动态路由与权限控制的正确姿势热搜里有vue动态路由这个关键词它对应的是后台管理系统最常见的需求不同角色登录后看到不同的菜单和路由。很多新手在这里犯的错误是想直接在路由守卫里判角色拦截,但一个完整的权限路由方案包含两层菜单和路由都得动态生成。我常用的做法是基础路由登录页、404写死在配置里业务路由在后端返回的菜单数据基础上用router.addRoutes()动态注册。注意 Vue Router 3 的addRoutes是数组参数Vue Router 4 改成了addRoute单个添加别搞混。// 登录成功后根据后端返回的路由表动态生成 const accessRoutes generateRoutes(menuList) router.addRoutes(accessRoutes) next({ ...to, replace: true })generateRoutes的核心逻辑也不复杂遍历菜单数组把配置项映射成 Vue Router 能识别的路由对象同时动态import()对应组件。这里最重的组件路径映射问题建议维护一个组件路径对照表不要用字符串拼路径Webpack 打包后路径会变字符串拼的组件全都会加载失败。3.3 路由参数的两个经典坑路由传参是面试高频题也是实战里出问题最多的地方。this.$router.push({ path: /detail, query: { id: 123 } })传参目标页用this.$route.query.id接收这个简单可靠URL 上能看到参数。另一种是this.$router.push({ name: Detail, params: { id: 123 } })用this.$route.params.id接收但注意params参数在刷新页面后会丢失因为它只存在于内存中的路由记录里不会写进 URL。很多人栽在这里刷新后页面空了还以为是状态管理的问题。另一个坑是同一路由组件复用但参数变了的情况。比如/detail/1切换到/detail/2如果两个路由都指向同一个Detail.vue组件Vue 会复用组件实例导致created生命周期不再执行页面数据不会自动更新。解决办法是在组件里监听$routewatch: { $route.params.id: { handler(newVal, oldVal) { this.fetchDetail(newVal) }, immediate: true, }, }这个immediate: true很关键它让组件初始化时也执行一次省去显式调用created里的初始化方法。我见过不少人只写handler不带immediate结果第一条数据死活不加载。4. 页面集成全家桶M3U8播放、地图、图表、拖拽的真实落地4.1 浏览器播放M3U8绕过插件时代的hls.js方案热搜里vue播放m3u8免安装被搜得很频繁。M3U8 本质是一个文本索引文件里面列了一堆 TS 视频分片的地址。浏览器原生video标签不认 M3U8早几年大家靠 Flash 插件来解决现在 Flash 停了主流方案就是两个hls.js或者video.js配合其 HLS 插件。hls.js 是纯前端方案它会把 M3U8 拉下来通过 Media Source ExtensionsMSE把分片喂给video实现免安装直接播。在 Vue 2 组件里使用的方式很简单import Hls from hls.js mounted() { const video this.$refs.video if (Hls.isSupported()) { const hls new Hls() hls.loadSource(src) hls.attachMedia(video) hls.on(Hls.Events.MANIFEST_PARSED, () { video.play() }) } }如果你的项目里已经用了 video.js也可以用videojs-contrib-hls插件在video.js内部完成类似工作。需要提醒的是M3U8 或者 TS 分片如果跟你的前端页面不在同一个域名下服务端必须返回正确的 CORS 头否则浏览器会拦截分片请求黑屏加报错。这个属于服务端配置前端解决不了排查时先看 Network 面板有没有跨域错误。至于欢乐谷这类带具体地点的 M3U8 搜索词本质还是同一个技术诉求——在网页里播放现场或景区的视频流。处理逻辑跟上面完全一样不需要额外的特殊代码。4.2 地图选型Mapbox 与腾讯地图在显隐层面的取舍地图集成也是 Vue 2 项目里的高频需求。基于热搜里mapbox vue和用在vue里的腾讯地图两个词我对比一下两条路线。Mapbox GL JS 胜在样式自由度高矢量瓦片渲染细腻3D 效果惊艳前端生态也活跃。但它用的是 Google 的墨卡托投影和自己的瓦片服务在国内访问速度不稳定、加载缓慢是常态而且底图数据的合规性在业务项目里是个麻烦事。做国内业务系统我不太推荐除非你们的美术设计要求极高且能解决瓦片加载问题。腾讯地图的优势是中文文档全、瓦片加载速度快、与微信生态契合而且 Vue 2 项目里用起来几乎不需要做任何魔法转换。基本步骤是在index.html引入地图 SDK 脚本然后在组件里实例化。export default { data() { return { map: null, marker: null, } }, mounted() { const center new TMap.LatLng(39.98412, 116.307484) this.map new TMap.Map(this.$refs.mapContainer, { center: center, zoom: 14, }) this.marker new TMap.Marker({ map: this.map, position: center, }) }, beforeDestroy() { // 地图销毁防止内存泄漏 if (this.map) { this.map null } }, }地图实例的正确销毁经常被忽略组件卸载后setMap(null)释放资源否则页面切换多了整个浏览器开始卡顿。4.3 ECharts 双柱状统计图与 Canvas 2D 的细节vue中用echarts画两个柱状统计图这类需求说白了就是 ECharts 中配置两个bar系列。听起来简单但有几个参数直接决定了图长什么样option { tooltip: {}, legend: { data: [2023年, 2024年], }, xAxis: { type: category, data: [一月, 二月, 三月, 四月], }, yAxis: { type: value, }, series: [ { name: 2023年, type: bar, barGap: 20%, barWidth: 24, data: [820, 932, 901, 934], }, { name: 2024年, type: bar, barWidth: 24, data: [1004, 1100, 1200, 1330], }, ], }barGap控制同一个类目下两个柱子的间距默认是30%调小是两个柱子贴得更紧调大是分开。还有一个barCategoryGap控制不同类目之间的间距。双柱状图常见的问题是柱子过宽或过窄、跟配色的可读性差我用一个表格把几个关键参数列一下参数作用常用值barWidth柱宽固定像素20-30barGap同类别下不同系列的间距百分比0% - 30%barCategoryGap类目间的间距百分比20% - 40%label柱子顶部数值显示{ show: true, position: top }ECharts 在 Vue 2 里的生命周期绑定是另一个容易出错的点。必须把所有初始化逻辑放在mounted里因为这时候 DOM 节点才存在。如果你用了v-if控制图表容器的显隐记得在容器渲染完成后再初始化方法是用$nextTick回调。组件销毁时一定要myChart.dispose()否则重复创建图表实例会带来矩阵式内存泄漏切几次页面就卡顿。Canvas 2D 在 Vue 2 里的坑和 ECharts 类似本质都是拿 DOM 引用 处理生命周期。用this.$refs.canvas拿节点然后canvas.getContext(2d)绘制。要注意的是Canvas 的style.width和attribute.width是两回事前者是 CSS 尺寸后者是绘制分辨率不匹配会导致图形模糊。做响应式 Canvas 时建议在window.resize事件里重新设置canvas.width并重绘同时记得beforeDestroy的时候移除监听。4.4 拖拽组件生成页面低代码思路在 Vue 2 的落地vue拖拽组件生成页面代码这个热搜词后面站着的是一大批搞低代码、配置化表单和可视化搭建平台的开发者。Vue 2 项目里实现拖拽生成页面核心不是一个拖拽库而是组件树到渲染器的设计思路。我推荐用vuedraggable它封装了 Sortable.js在 Vue 2 里配合v-model就能实现列表拖拽排序。真正复杂的部分是画布的渲染。我的做法是定义一套统一的组件描述数据结构// 组件描述对象 { type: input, // 组件类型 props: { // 传给组件的属性 label: 姓名, placeholder: 请输入姓名, required: true, }, children: [], // 子组件 }画布接收一个组件描述数组用动态组件component :iscompMap[item.type] v-binditem.props /渲染拖拽只是改变数组顺序或者向数组中插入新对象。最后想生成页面代码本质上就是把这份数据序列化输出要么输出 JSON要么写个模板函数递归生成 Vue 模板字符串然后交给后端存储。下次打开时反序列化这份数据画布立刻还原这就是一套最简单的低代码闭环。这个方案比接一堆可视化拖拽平台要实在得多代码量不大产物可控也容易扩展新组件。我实际做过一个跨平台的后台管理页面搭建器用的就是这个思路稳定运行了一年多。5. 打包进SpringBoot构建配置与前端路由的最后一公里5.1 相对路径配置是第一步Vue 2 项目打包后要放进 SpringBoot第一个必改项就是vue.config.js里的publicPath。默认值是/意味着打包后的静态资源路径是/js/app.js这种绝对路径部署到服务器某个子路径或者直接用 SpringBoot 的静态资源目录时资源路径全断掉。改成相对路径// vue.config.js module.exports { publicPath: ./, outputDir: dist, assetsDir: static, productionSourceMap: false, }publicPath: ./会让打包后的index.html里引用资源变成相对路径比如./static/js/chunk-vendors.js。这样你把整个dist目录拷到 SpringBoot 的src/main/resources/static下启动后访问http://localhost:8080/index.html就能直接打开页面。5.2 history路由模式的最后一公里配置如果你的前端路由用的是history模式直接扔进 SpringBoot 会有个大坑用户在http://localhost:8080/dashboard刷新页面SpringBoot 找不到/dashboard这个映射直接给你一个 404。原因很简单——路由跳转是前端 JS 控制的但刷新是浏览器向服务器发起的真实 HTTP 请求服务器不认识前端路由的路径。解决思路是把不存在的路径统统转发到 index.html让前端路由接管。在 SpringBoot 里写一个简单的转发 ControllerController public class ForwardController { RequestMapping(value /{path:[^\\.]*}) public String forward(PathVariable String path) { return forward:/index.html; } }注意这个正则[^\\.]*排除了带点的路径目的是让/js/app.js、/static/xxx.css这类静态资源请求不被拦截只有不带点的路径才转发到 index.html。官方推荐案例如下如果你的静态资源也有点号路径比如api.xxx之类的接口路径要再精细调整白名单加一层判断。相比之下hash模式就完全不用配这个转发URL 里#后面的内容本来就不会发到服务器部署省心很多。但如果产品对 URL 美观有要求强制要history模式那就老老实实配转发别偷懒。这里也提一下SpringBoot 2.6 以上如果配了拦截器,要先放行/index.html和/static/**否则拦截器优先级高于转发 Controller照样 404。提示:前端部署的这种“转发到 index.html”逻辑在 Nginx 里对应的是try_files $uri $uri/ /index.html思路一样。如果你未来要换部署环境记住这个核心思路就能触类旁通。5.3 前端源码与打包产物怎么发给别人热搜词vue项目源码怎么发给别人回答起来其实很简单源码发送时删掉node_modules把package.json、锁文件、.nvmrc、.npmrc一起发给对方对方装好对应 Node 版本后执行npm install就能跑起来。大文件的坑主要在于有些人习惯把dist也发出去其实没必要dist是可以随时重新构建的产物。我建议在项目里放一个简洁的README.md写清楚以下内容Node 版本、包管理器npm 还是 yarn、安装命令、启动命令、打包命令、后端接口地址配置在哪里。我见过太多团队交接项目时只丢一个压缩包收到的人光环境就折腾一整天最后还怪项目有问题。README 花十分钟写至少能省下对方两小时这笔账怎么算都划算。6. 面试与学习路线Vue 2经验在今天还有没有价值6.1 面试官到底想问什么Vue 2八股的核心脉络从热搜词vue面试题和vue前端面试题的热度看这个话题还会持续很久。面试官问 Vue 2 的问题翻来覆去就那几个核心点但要真正答好需要你理解而不是背答案。响应式原理是永远跑不掉的题Vue 2 通过Object.defineProperty给对象的属性定义 getter 和 setter在 getter 里收集依赖Watcher在 setter 里触发更新。正因为它只能劫持属性新增属性和删除属性才没有被劫持所以需要Vue.set和Vue.delete。数组的索引变化也监听不到所以 Vue 2 重写了数组的几个方法。这个原理直接解释了为什么对象的响应式有局限比单纯背双向绑定要有说服力得多。组件通信方式是第二高频考点。props/$emit用于父子通信event bus用于任意组件通信Vuex用于全局状态管理$attrs/$listeners用于多级透传provide/inject适合跨层级依赖注入ref用于直接调子组件方法。每个方案的适用场景都能说出个一二三才算过关。生命周期、computed和watch的区别、v-if与v-show、插槽slot的三种用法默认插槽、具名插槽、作用域插槽、nextTick的实现原理、虚拟 DOM 和 diff 算法中的 key 作用这些都是 Vue 2 面试题的基础盘。我梳理过一份高频面试问题列表放在下面供自测主题高频问题关键回答点响应式原理Vue 2 如何监听数据变化Object.defineProperty、依赖收集、数组重写生命周期created 和 mounted 的区别created 拿不到 DOMmounted 能操作 DOM组件通信跨层级组件如何通信provide/inject、$attrs、event buscomputed/watch什么时候用 computed 什么时候用 watch派生状态用 computed异步/开销大用 watch虚拟DOMkey 的作用是什么最小化 diff、避免复用错乱nextTick用于解决什么问题DOM 更新后回调微任务优先6.2 一份务实的 Vue 2 快速学习路线如果你现在才刚开始学 Vue我给一条能快速落地的路径。第一步先别碰 CLI 脚手架直接打开官网文档把模板语法、指令、计算属性和生命周期过一遍用 CDN 方式写几个小 Demo理解组件化思维。第二步装 Vue CLI 搭一个正经项目跑通路由、Vuex、Axios做一个小的管理系统。第三步去 GitHub 找一个完整的开源后台项目实战像热搜里那个黑马程序员的前端 vue 项目《ihrm人力资源后台管理》这种教学型项目胜在配套视频和文档齐全适合照着做一遍并研究它的架构。学习过程中有一点要特别注意不要浪费时间研究 Vue 2 已经被淘汰的边缘 API比如filter过滤器、$on/$off这类事件 API。Vue 3 里这些都被删了学了只会给自己增加负担。重点放在那些能迁移到 Vue 3 的知识上组件通信思想、Vuex 状态设计思路、路由守卫、插槽作用域。这些底层思维不随版本变化。6.3 迁移到 Vue 3 前的最后一个建议我的个人经验是Vue 2 和 Vue 3 的差异没有想象中大。选项式 API 和组合式 API 是写法层面的差异但响应式原理、虚拟 DOM、组件通信的知识框架是一脉相承的。很多团队从 Vue 2 迁移到 Vue 3真正耗费精力的地方在生态库替换Vuex 换 PiniaVue Router 3 升 4Element UI 换 Element Plus自定义指令和过滤器的改造。所以我的建议是如果手上没有项目包袱直接学 Vue 3 也没问题但如果你正好要维护值钱的老项目那么 Vue 2 的经验不是白学反而是你理解现代前端框架演进的绝佳参照系。知道 Vue 2 的Object.defineProperty的局限你就更能理解为什么 Vue 3 用 Proxy知道 Vue 2 的 Options API 组件里逻辑分散在data、methods、computed各处的痛点你才更懂 Composition API 在逻辑复用上的价值。这些为什么层面的理解才是面试官真正认可的东西。最后再分享一个实战技巧。如果你要在一个老项目里快速验证一个小功能比如 Mock 接口数据可以引入 jsonmock 这类工具简单配置一下就能拦截请求返回假数据不侵入业务代码。等到联调阶段再替换成真实接口。这种小工具能明显提升开发体验值得在团队里推广。希望这篇围绕 Vue 2 项目和热搜场景展开的梳理能在某个环节帮到你——不管是环境搭建、路由重构、第三方集成还是面试备考。说到底技术工具会迭代但解决问题的思路和方法才是真正能带走的东西。
返回列表