ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Vue 1.26实战复盘:从环境搭建到路由、流媒体与数据接入全攻略

Vue 1.26实战复盘:从环境搭建到路由、流媒体与数据接入全攻略 看到“Vue 1.26”这个标题先别误会这真不是 Vue 官方出了 1.26 版本官方现在还在 3.x 的路线里往前走。这串编号其实是我手里一个真实业务项目的迭代代号第1轮开发打到第26个里程碑的时候刚好把 Vue 相关的核心链路都过了一遍环境配置、路由参数、动态路由、插槽复用、SSE 数据对接、m3u8 视频播放、PDF 预览、地图接入、Canvas 画图、ECharts 柱状图、多文件上传最后还打包塞进了 Spring Boot 里。所以我干脆把这轮复盘整个叫做“Vue 1.26”这既是项目的版本代号也算是一份 Vue 全链路实战笔记。这篇内容适合谁如果你正在学 Vue准备做毕设里那个“基于SpringBootVue的商品管理系统”或者准备前端面试被问“Vue路由原理”“插槽怎么用”再或者在 VSCode 里点 Vue 标签死活跳不过去、m3u8 放了半天黑屏——这篇文章基本都能给你个能直接落地的答案。我会把每个模块怎么拆、为什么这么拆、踩过什么坑、怎么排查都按照实际开发顺序写出来你自己动手照着敲一遍基本上就能跑起来。1. 先从环境搭建说起Vue 安装与工程化准备的几个关键决策1.1 版本选型为什么我建议你直接选 Vue 3 Vite聊环境配置之前先解决一个让很多新人卡住的问题到底学 Vue 2 还是 Vue 3我这里直接给结论如果你不是要维护 2022 年之前的老项目新项目无脑 Vue 3 Composition API配套构建工具选 Vite不要再用 Vue CLI 那套 webpack 配置了。Vite 的优势在于开发服务器启动速度快因为它利用了浏览器原生 ES Module不用像 webpack 那样一上来就把所有模块打包一遍。项目小的时候体感不明显一旦页面多了几十个路由、组件几百个Vite 冷启动是秒级webpack 可能得等十几秒。这不是玄学是机制差异Vite 按需编译浏览器请求哪个模块才编译哪个模块。而你最终 npm run build 的时候它内部再用 Rollup 做生产打包产物同样可以经过优化不用担心上线体积失控。至于“Nuxt 和 Vue 的区别”简单说一句Vue 是前端框架本身Nuxt 是在 Vue 之上做了一层更高阶的应用框架核心加了服务端渲染、文件路由、自动导入之类的能力。如果做后台管理系统、中后台工具Vue 单页就够用如果做需要被搜索引擎收录的官网、内容站那时候才值得考虑 Nuxt。不然你光是为了 SEO 上 Nuxt还得学习 Node 服务端部署的整套链路成本翻倍。1.2 安装依赖的完整步骤与镜像源配置开了新项目第一步永远是安装 Node.js。这里有个容易踩的坑不要图省事去官网下最新的 major 版本很多 CLI 工具和依赖包对 Node 版本有要求太新的版本可能出现原生模块编译失败。像我用的 Node 18 LTS / 20 LTS 这种长期维护版本就挺稳。装好后先看一眼版本node -v npm -v然后因为国内网络环境的问题强烈建议先设置 npm 镜像源。这里注意别用“代理”那套方案直接用 registry 指到国内镜像就行npm config set registry https://registry.npmmirror.com这一步能解决大部分“npm install 卡半天最后报错”“依赖装到一半网络超时”的问题。我之前带新人时发现他们一上午啥也没干就在跟 node-sass 的下载超时搏斗后来统一配了镜像源三分钟装完。虽然现在项目很少用 node-sass 了但依赖下载慢的本质还是网络所以先配源永远不会亏。接着创建项目。Vite 官方脚手架命令很好记npm create vitelatest vue-1-26 -- --template vue cd vue-1-26 npm install npm run dev注意一下Vite 官方模板默认只给最基础的目录结构如果要写一个带路由和多页面的项目你还得手动装 vue-router 和状态管理库npm install vue-router4 pinia axios如果你打算做后台管理系统大概率要配一个 UI 组件库。现在 Element Plus 用得最多装是很好装但建议你直接配自动按需导入否则全量引入会让首屏包特别大。npm install element-plus npm install -D unplugin-auto-import unplugin-vue-components然后去 vite.config.js 里挂上这两个插件。按需导入的意义是你只用 Button、Table 等几个组件打包时只把用到的组件和样式打进产物里而不是把整个 UI 库塞进去。别小看这一步我见过有人全量引入 Element Plus首屏 JS 直接多了 1MB 多。1.3 目录结构别等代码乱了再重构老生常谈的目录设计但确实管用尤其项目过了几千行之后。我常用的结构是这样src/ ├── api/ # 接口请求封装 ├── assets/ # 静态资源 ├── components/ # 全局公共组件 ├── composables/ # 组合式函数比如 useAuth、useUpload ├── router/ # 路由配置 ├── stores/ # Pinia 状态 ├── styles/ # 全局样式 ├── utils/ # 工具函数 ├── views/ # 页面级组件 ├── mock/ # Mock 数据 └── main.js不要把所有东西都堆在 views 里也别把几百行的业务逻辑全写进 setup 里。把可复用的逻辑抽到 composables比如“列表页的加载、分页、刷新”这组逻辑抽成 useTable 之后每个新列表页都能少写一半代码。组件层面至少分两层页面组件只管数据拿取和状态流转展示组件只负责接收 props、触发事件。你后期调样式、换交互时会感谢这个划分。2. Vue 路由实战从基础路由到动态路由再到权限控制2.1 路由参数params 和 query 的正确打开方式Vue Router 4 的 API 跟 Vue 2 时代有变化最明显的一点是要用 createRouter 而不是直接 new VueRouter。基础路由配置没什么好说的关键是页面里怎么拿参数。定义路由时用冒号标识动态段const routes [ { path: /user/:id, component: () import(/views/UserView.vue) }, { path: /search, component: () import(/views/SearchView.vue) } ]在组件内部拿参数推荐用组合式 API 里的 useRouteimport { useRoute } from vue-router const route useRoute() const userId route.params.id const keyword route.query.keyword这里提醒一个很容易遇到的坑当你从/user/1跳转到/user/2如果复用同一个组件实例组件的 setup 不会重新执行route.params 虽然变了但页面内容不会自动刷新。解决办法是在组件里监听 route.paramswatch(() route.params.id, (newId) { fetchUser(newId) })至于 params 和 query 怎么选经验是query 适合可分享、可收藏的筛选条件因为它直接拼在 URL 上比如/search?keywordvue别人复制链接打开也能看到同一结果。params 更适合内部跳转用的标识尤其是那种不需要暴露给用户的 ID。2.2 动态路由权限系统的核心实现方式后台管理系统里经常遇到这种场景不同角色登录后看到的菜单不一样。你要的绝对不是把所有路由写死而是根据后端返回的菜单结构动态注册路由。Vue Router 4 提供了 addRoute 方法可以在运行时往路由表里追加一条或多条记录。再加上 Vite 的 import.meta.glob可以把 views 目录下的组件映射成异步组件实现真正的按需加载const modules import.meta.glob(../views/**/*.vue) const menu await fetchMenuByUser() // [{ path: /goods, component: goods/index }] menu.forEach(item { router.addRoute(layout, { path: item.path, name: item.name, component: modules[../views/${item.component}.vue] }) })为什么这里要用 import.meta.glob因为动态路由的组件路径是后端返回的字符串打包工具静态分析时并不知道你最终会用到哪个 .vue 文件。用 glob 等于提前告诉 Vite“views 下面所有文件我都有可能加载你帮我全量收集起来”然后运行时再按下标取出对应的模块。这样既保住了按需加载又能实现动态映射。用 addRoute 之后有一个特别容易踩的坑路由已经加进去了但当前页面刷新后白屏或者在菜单里点击不生效。原因通常是路由 addRoute 是异步的操作完后最好使用 router.replace 重新触发一次导航并且不要直接在 beforeEach 里用 addRoute因为守卫执行时路由表还没更新完毕容易造成无限循环。我的做法是先调接口拿菜单再 addRoute最后 replace 到目标页面整个过程用状态标记一下避免守卫重复进入。2.3 路由守卫登录拦截不只靠 if很多新手做登录拦截喜欢在组件里写if (!token) router.push(/login)这种写法不是不行但每个页面都写就太碎了。统一放在全局前置守卫里更清晰router.beforeEach((to, from) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { return { name: login, query: { redirect: to.fullPath } } } return true })这样可以在 meta 里标记“这个路由需要登录才能访问”逻辑集中在一起。另外 redirct 参数是实际开发里很加分的小细节用户被踢回登录页后登录成功可以跳回原来的目标页而不是永远在首页。很多系统没做这个用户每次登录完还得手动找回刚才看的页面体验差别很大。3. 插槽与常见 UI 封装折叠展开、超出隐藏的处理细节3.1 slot 的三种形态以及作用域插槽解决什么问题插槽是 Vue 组件复用里最重要的机制之一面试也爱问。简单理解插槽就是父组件给子组件预留的“坑位”具体坑里填什么内容由父组件决定。默认插槽和具名插槽是基础真正难理解的是作用域插槽。我举个例子你封装了一个表格组件表格负责渲染行和列但某一列需要根据当前行数据显示自定义内容比如状态标签、操作按钮这时候子组件只知道数据不知道父组件想渲染成什么样。把数据交给父组件渲染就是作用域插槽的用法!-- BaseTable.vue -- template div classbase-table slot nameoperation :rowdata/slot /div /template父组件使用BaseTable :datalist template #operation{ row } el-button typedanger clickdeleteItem(row.id)删除/el-button /template /BaseTable对比一下如果你用 props 传一个 render 函数进子组件也能实现但作用域插槽在模板语法层面更直观也让子组件保持“只管数据不管长什么样”的原则。3.2 内容折叠展开与 div 超长隐藏的兼容写法“Vue 内容折叠展开”和“Vue div超出隐藏”也是高频问题。折叠展开最笨但最容易控制的做法是用 v-show 切换不过没有动画。想要平滑动画推荐用 Vue 的 transition 组件 合理高度变化或者直接控制 style 的 max-height。注意这里别图省事直接元素高度从 0 变 autoCSS transition 对 auto 值不生效所以要么用 max-height 从 0 变一个足够大的值要么用 JS 测量实际高度再过渡。超出隐藏分单行和多行。单行很简单.ellipsis { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }多行省略前端标准方案是 -webkit-line-clamp.ellipsis-2 { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }兼容性上现代浏览器基本都支持但老一点 WebView 有概率不识别 display: -webkit-box稳妥做法是先给一个固定高度兜底再叠加多行裁剪样式。实际项目里我都是这么写先 overflow: hidden 保证内容不溢出再用 line-clamp 做裁剪。4. 数据接入层Mock、SSE、Spring Boot 打包一次说清4.1 Mock 版本里“增加修改删除”是怎么体现的热词里有个很具体的问题“Vue mock版本 增加修改删除mock是怎么体现的”。我猜你是想说前后端还没联调时如何在开发环境把增删改查完整跑通。Mock 的本质是模拟一个接口替身前端请求的参数和返回的结构都跟真实接口保持一样切换环境时只改一个开关就行。用 vite-plugin-mock 可以很清爽地做到。在 mock 目录里建一个同名接口文件import { MockMethod } from vite-plugin-mock let list [ { id: 1, name: 商品A, price: 20 }, { id: 2, name: 商品B, price: 30 } ] export default [ { url: /api/goods/list, method: get, response: () ({ code: 0, data: list }) }, { url: /api/goods/add, method: post, response: ({ body }) { const newItem { id: Date.now(), ...body } list.push(newItem) return { code: 0, data: newItem } } }, { url: /api/goods/update, method: put, response: ({ body }) { const idx list.findIndex(item item.id body.id) Object.assign(list[idx], body) return { code: 0 } } }, { url: /api/goods/delete, method: delete, response: ({ query }) { list list.filter(item item.id ! Number(query.id)) return { code: 0 } } } ]然后前端 axios 请求时URL 直接写成/api/goods/list开发环境下 mock 插件会在 Vite 服务层拦截请求并返回上面的数据。这里“mock 怎么体现出来的”就清楚了你根本没有启动后端但前端能看到真实接口返回效果这就是 mock 的价值——它让前后端并行开发成为可能。等到真实后端接口好了你只要在 vite.config 里把 mock 开关关掉前端代码一行不用改。4.2 Vue 里接 SSE用 EventSource 还是 fetch如果你的系统需要做实时数据推送比如后端跑批任务时向前端推进度、运维平台实时输出日志你有两个选择WebSocket 和 SSEServer-Sent Events。WebSocket 是双向的适合聊天、协作编辑SSE 是单向的服务端推到客户端基于普通 HTTP断了还能自动重连。如果只是后端往前端推数据我强烈建议直接用 SSE省心太多了。Vue 里接 SSE 的代码非常简单const es new EventSource(/api/task/progress) es.onmessage (event) { const data JSON.parse(event.data) progress.value data.percent } es.onerror () { // 断线后 EventSource 会自动重连这里只需要处理状态提示 }有个关键点EventSource 建立连接后持续占用一个 HTTP 连接组件销毁时一定要调用 es.close()否则后端会一直维护这个连接连接一多就把服务端线程池打满了这是个很隐蔽的内存和连接泄漏问题。另外如果你的项目有跨域后端记得把 Access-Control-Allow-Origin 配好还要设置 Access-Control-Expose-Headers否则某些浏览器下 EventSource 可能一直是 CONNECTING 转不动。顺带一提后端如果用 Java Spring Boot 写 SSE核心就是返回 MediaType.TEXT_EVENT_STREAM每隔一段写一个 “data: {...}” 格式的字符串用 Python FastAPI 则用 StreamingResponse一样的道理。4.3 前端打包放进 Spring BootHash 路由还是 History 路由“Vue 打包放进SpringBoot中”是毕设和中小公司项目里的高频需求。做完 npm run build 后dist 里就是一堆静态文件和一个 index.html。最省事的方式是把 dist 目录整个拷进 Spring Boot 的 src/main/resources/static 下启动后端后直接访问http://localhost:8080/index.html。但这里有个大坑如果你前端用的是 History 模式路由vue-router 默认的那种URL 是好看的/user/list而不是/#/user/list你直接访问http://localhost:8080/user/list会 404因为后端静态文件处理里没有这个路径它只能找到 index.html找不到 /user/list 这个资源。解决办法两个。第一个是偷懒但可靠的做法路由模式改成 createWebHashHistoryURL 变成http://localhost:8080/#/user/list一切静态资源都在 hash 后面后端不需要任何配置。缺点是 URL 不太好看而且共享链接时 hash 不会传给服务器但中小系统里完全够用。第二个是更规范的做法后端加一个转发 Controller把所有不带后缀的非静态路径都转发到 index.html。Spring Boot 大概是这么写Controller public class SpaForwardController { RequestMapping(value {/, /{path:[^\\.]*}, /{path:[^\\.]*}/{sub:[^\\.]*}}) public String forward() { return forward:/index.html; } }正则里排除带点号的路径是为了不让静态资源js、css、png 等也被转发走。这样做完直接刷新任意前端路由都是正常的。我还要建议一点部署到生产环境别把前端静态文件跟后端打在一个包里的思路有一种局限因为前端一更新整个后端 jar 也要重新构建。只是对于课程设计、内部系统这类规模的项目省一台静态资源服务器确实香。5. 业务功能模块逐个拆m3u8、PDF、地图、Canvas、ECharts、上传5.1 m3u8 视频免安装播放用 Hls.js 实现浏览器内直播流m3u8 是 HLS 流媒体协议的索引文件浏览器原生不支持这种格式所以你直接放一个video srcxxx.m3u8多半是黑屏。所谓“免安装”指的是不用安装第三方播放器软件而是在网页里用 JS 解码播放。目前最主流的做法是引入 hls.js。安装npm install hls.js页面里这样用import Hls from hls.js const videoEl videoRef.value if (Hls.isSupported()) { const hls new Hls() hls.loadSource(https://example.com/live/stream.m3u8) hls.attachMedia(videoEl) hls.on(Hls.Events.MANIFEST_PARSED, () { videoEl.play() }) } else if (videoEl.canPlayType(application/vnd.apple.mpegurl)) { // Safari 原生支持 HLS videoEl.src https://example.com/live/stream.m3u8 }做这个功能时九成九的坑都在跨域。m3u8 里面的分片 ts 地址如果跟页面域名不一致而且后端没配 CORS 头Hls.js 加载切片时会直接报跨域错误。另外 HTTPS 页面里加载 HTTP 的流也会被浏览器拦截上线前把流地址统一换成 HTTPS 是基本要求。5.2 “Vue image 能显示 PDF 吗”这个问题的准确答案先说结论你真的想用img srcxxx.pdf直接显示 PDF靠不住。浏览器的行为并不统一有的会尝试渲染第一页有的直接显示下载框而且把 PDF 当图片显示你是没法翻页的。所以“vue image 能显示pdf吗”这个问题的答案很简单图片标签不适合渲染 PDF。想要在页面上内嵌 PDF最省事的是 iframeiframe :srcpdfUrl classpdf-viewer /这样浏览器自己会渲染 PDF 内容支持缩放和翻页部署简单。缺点是不同浏览器 UI 不太一样而且无法跟页面数据做深度交互。想完全掌控阅读体验就要用 pdf.js安装 pdfjs-dist然后用 Canvas 自己绘制每一页。这样做你可以自己写翻页按钮、页码跳转、缩放比例甚至可以做文字选择。代价是实现工作量不小大约需要小几百行代码。我的建议是页面只是临时预览就 iframe稳定省事如果这是产品核心功能比如电子合同签署、在线阅卷那就老老实实用 pdf.js 做二次开发。5.3 腾讯地图和 Mapbox 接入 VueAPI Key 与容器宽高的坑地图这一类功能热词里同时出现了“腾讯地图”和“Mapbox”说明很多人在 Vue 里接地图都踩过容器白屏。先讲共性问题地图初始化时如果外层容器宽高是 0地图就干脆什么都不显示。你布局里如果用的是 flex 并且没给容器显式高度要先把外层撑起来设置明确的 height 值再初始化地图。腾讯地图的接入相对直白去腾讯位置服务申请一个 Key然后在 index.html 引入 JS API 的 script再在组件里用腾讯地图的全局对象创建地图。要注意新一代腾讯地图 JS API 有配额限制个人开发选基础版本就够了。Mapbox 也是同样的思路但用的是 mapbox-gl 这个 npm 包样式和图层机制做得更强一点import mapboxgl from mapbox-gl import mapbox-gl/dist/mapbox-gl.css mapboxgl.accessToken import.meta.env.VITE_MAPBOX_TOKEN const map new mapboxgl.Map({ container: mapContainer.value, style: mapbox://styles/mapbox/light-v10, center: [116.39, 39.9], zoom: 11 })强烈建议把 token 放到 .env 文件里而不是直接写死在代码里。因为你的前端代码一旦打包发布token 都会暴露在浏览器端写死之后如果 token 泄漏控制台里很快就收到一条恶意调用通知。5.4 Canvas 2D 与 ECharts一次画两个柱状统计图Canvas 在 Vue 里用其实就是把 canvas 元素绑到 ref 上等组件挂载后再拿 getContext(2d)。很多新手会犯一个错在 setup 里同步去拿 ref但此时 DOM 还没渲染拿到的 canvas 是 null。记住用 onMounted 或者 nextTick。如果只是做统计图表我的建议是别自己用 Canvas 手搓坐标轴直接用 ECharts。它底层本身也是 Canvas也可以切 SVG 渲染但封装好了各种图表、动画、Tooltip、图例。画两个柱状统计图其实是很容易的核心是把 series 数组里配两组数据import * as echarts from echarts import { onMounted, onBeforeUnmount, ref } from vue const chartDom ref(null) let chart null onMounted(() { chart echarts.init(chartDom.value) chart.setOption({ tooltip: { trigger: axis }, legend: { data: [本月, 上月] }, xAxis: { type: category, data: [周一, 周二, 周三, 周四, 周五] }, yAxis: { type: value }, series: [ { name: 本月, type: bar, data: [120, 200, 150, 80, 170] }, { name: 上月, type: bar, data: [90, 150, 120, 60, 110] } ] }) }) onBeforeUnmount(() { chart?.dispose() })这里我要重点强调两种回调的区别echarts.init 要等容器有实际宽度高度否则图表会渲染成 0 乘 0 的空白。所以如果你的图表放在 el-tabs、折叠面板这种延迟渲染的容器里一定要在内容真正显示后再初始化或者在 tabs 切换事件里加一个 chart.resize()。另外组件卸载时一定要 dispose否则切换路由后旧图表实例还在监听窗口 resize容易出现内存泄漏和报错。5.5 多文件上传别手动设置 Content-Type多文件上传在 Vue 里的实现最容易跪在 FormData 上。先看代码function handleFileChange(event) { const files Array.from(event.target.files) const formData new FormData() files.forEach((file) { formData.append(files, file) }) axios.post(/api/upload, formData, { onUploadProgress: (progressEvent) { const percent Math.round((progressEvent.loaded / progressEvent.total) * 100) uploadProgress.value percent } }) }不少人在写这段代码时习惯性地给 axios 的 headers 加一行Content-Type: multipart/form-data结果后端反而收不到任何文件。原因是 multipart 请求最关键的 boundary 字符串由浏览器自动生成你一旦手动指定 Content-Typeboundary 就对不上了后端按 boundary 去切分数据自然切不出来。最正确的做法是交给 axios 自动处理不手动设置该字段。同理使用 ul 组件上传时也要注意别在额外的地方改写请求头。5.6 Canvas 2D 的应用场景补充我简单补充一下 Canvas 2D 的定位。ECharts 能覆盖九成图表需求但如果你想画的东西不是标准图表比如签名板、图片裁剪框、自定义流程图节点、涂鸦白板那就必须直接用 Canvas 原生 API。Vue 环境下核心还是那几步ref 拿 DOM、onMounted 里初始化、onBeforeUnmount 里清理。很多仿大厂的白板工具都是基于 Canvas 2D 再加 Pointer Events 封装的。你不会天天画但必须知道 Canvas 和 DOM/CSS 的边界在哪。6. 开发调试与面试高频考点别让工具坑了你6.1 VSCode 中点击 Vue 标签不跳转的真相写 Vue 3 项目最让人恼火的体验之一就是点了 template 里的组件名VSCode 不给你跳转定义。原因八成是你装了 Vetur这个老插件是 Vue 2 时代的对 Vue 3 的 SFC 支持不完整。解决办法很明确禁用 Vetur安装 Vue - Official原 Volar并且确保它在你项目里被激活。装完之后如果你还跳不过去检查一下 tsconfig.json 或 jsconfig.json 里的路径别名是否配置了 符号映射不然编辑器不知道/components/xxx.vue指向哪个目录。配置示例{ compilerOptions: { baseUrl: ., paths: { /*: [src/*] } } }配完之后模板里的标签跳转、类型提示基本就恢复正常了。这个状态用文字说不直观但编辑器的智能提示是否能识别到符号一眼就能看出来。6.2 Vue UI 面板和 CopilotKit 这类辅助工具怎么用“vue ui”这个热词一般是 Vue CLI 时代的产物运行 vue ui 之后会在浏览器打开一个图形化面板可以直观看到依赖列表、执行 build/dev、可视化修改配置。如果你是 Vite 项目就没有官方对应的 vue ui 面板了不过日常开发其实也用不上图形面板命令行足够。我唯一用过它的场景是帮不太熟命令行的同学远程排查看依赖版本图形界面确实更直观。至于 “CopilotKit vue”这属于最近很火的 AI 编程应用组件方向。CopilotKit 这类库提供的是开箱即用的 AI 对话 UI 和消息管理目标是让前端快速集成一个聊天机器人式的辅助能力。如果你要在 Vue 应用里做生成式 AI 功能可以关注这类方案但落地前先确认好你的接口层是直连大模型 API还是走后端网关转发。前者要考虑密钥暴露风险后者需要后端配合。6.3 Vue 前端面试高频题从 v-model 到虚拟 DOM既然热词里专门有“vue前端面试题”和“vue面试题”我就挑几个最常被问到的题目展开说一下面试官真的爱问这些。第一个是 v-model 的原理。很多人能背出“语法糖”三个字但细节说不清。在 Vue 3 中v-model 等价于绑定 modelValue 这个 prop同时监听 update:modelValue 事件。所以自定义组件如果想支持 v-model就得定义 props.modelValue然后通过 emit(update:modelValue, value) 来更新。面试官如果追问“v-model 可以用在自定义组件吗”你把这个链路说清楚就稳稳加分。第二个是 key 的作用。diff 算法在对比新旧虚拟 DOM 时会尽量复用相同 type 的节点key 就是帮助 diff 判断“这个节点是不是原来那个节点”的依据。列表项尽量不要用 index 当 key因为删除中间项或重新排序时用 index 会导致错误的节点复用进而引发状态错乱比如输入框内容跟着行跳。我用一个实际例子给新人解释你把 key 想象成每个人身上的身份证号index 只是排队序号队伍顺序变了身份证号不变但排队序号全变了拿序号找人自然容易找错。第三个是 computed 和 watch 的区别。computed 是响应式缓存的计算值只有依赖变化时才重新求值watch 是副作用监听适合执行异步操作或者多个数据变化后的逻辑。能记住“computed 用于计算派生状态watch 用于执行副作用”就够用了。第四个是为什么 v-for 里不要同时用 v-if。因为 v-for 的优先级更高列表每渲染一项都会先判断一次 v-if如果列表很长而你只是要过滤掉少量项会白白多跑很多次。正确姿势是先用计算属性筛出最终列表再在模板里只保留 v-for。7. 常见问题与排查技巧实录最后把我这一轮实操里最常遇到、也最容易被新手卡住的问题统一列一张速查表方便你遇到现象时直接对号入座。现象可能原因解决思路npm install 卡住/报错网络原因导致下载失败配置 npmmirror 镜像源后再安装必要时删除 node_modules 和 lock 文件重装VSCode 点标签不跳转装错插件 Vetur、路径别名没配置换 Vue - Official并配好 jsconfig 路径映射History 路由刷新 404后端没有 index.html 转发改 hash 路由或后端加转发 Controller页面白屏、控制台 Vite 报错路径别名或依赖版本冲突先看有没有未捕获的 import 错误逐个组件注释排除m3u8 视频黑屏跨域或 HTTPS 混合内容后端配 CORS流地址统一换 HTTPSiframe 打开 PDF 空白后端没返回 PDF 的 Content-Type确认接口响应头是 application/pdf地图组件白屏容器宽高为 0 或 token 缺失显式设置容器高度检查 token 是否正确ECharts 图表不显示容器不可见时 init改为容器显示后再 init并在窗口变化时调用 resize多文件上传后端收不到手动设置了 Content-Type去掉手动设置让浏览器自动带 boundary组件卸载后报错或内存泄漏定时器、EventSource、图表未清理在 onBeforeUnmount 里统一 close/dispose排查还有个通用心法先看控制台报错到底是前端还是后端返回的再缩小范围。比如接口 500 就看后端日志前端报错就看栈指向的是哪个模块不要一上来就重装依赖、清缓存。开发工具里 Network 面板永远是我的第一站接口通没通、响应什么状态一眼就知道。另外单独说一个我在多个项目里见过的坑Vite 项目里vite.config.js 里配置了 别名但编辑器里 TS 还是不认识代码能跑却到处标红。这种问题本质上是 tsconfig 和 vite 配置没同步两边都要配上别名缺一不可。记住这句话能帮你省下很多跟编辑器较劲的时间。还有个小技巧如果你在写动态路由调试时总是不停手动登录、刷新、看菜单可以在开发环境里临时写一个“一键重置路由”的调试按钮把 router.removeRoute 和重新 addRoute 串起来想还原现场就点一下。虽然这种代码不该进生产环境但开发期效率提升是实打实的。写在最后说实话这一轮“Vue 1.26”项目跑下来我最深的感觉是Vue 本身的语法并不是最大的门槛真正拦住新人的全是在工程化边缘地带的琐碎问题——依赖装不上、路由刷新 404、标签跳不了、流媒体跨域黑屏、图表容器高度为 0。这些问题单独看都不难但它们会在你毫无防备的时候跳出来消耗你一整个下午。所以我写这篇复盘时刻意没有只讲知识点而是尽量把每个坑的“现场感”还原出来让你知道出问题长什么样、用什么思路去排查、最优解是什么。你不需要一次性读完所有章节完全可以把它当成一份“靶向速查手册”遇到了对应的问题翻到那一节照着做能跑通就行。等到你把这里面的链路都亲手走一遍再回头看 Vue你会发现所谓的前端框架其实也就是一套把状态和界面高效绑定起来的工具而你已经具备了驾驭它的经验。以后再有新项目可能你还会想起这次“Vue 1.26”的复盘对着目录心里默念一句这条路我走过。
返回列表