刚开始看到“vue.9”这个标题时,我第一反应是某个版本号,比如 Vue 2.9 或者某本书的第 9 章。但联系到近期大家频繁搜索的 vue 安装、路由传参、m3u8 播放、前后端分离 token 处理这些关键词,我明白了——这不是一个具体的插件或者某个单一项目,而是一整套围绕 Vue 的技术栈整理。简单说,这就是一条从入门到实战的完整路线图:环境怎么搭、路由怎么写、状态管理怎么选、视频怎么播、后端怎么连、打包后布局乱了怎么排查。
这篇文章面向的是两类人:一类是刚接触 Vue 不久,被茫茫多的工具链和概念搞得有点晕的初学者;另一类是已经能写一些页面,但遇到路由守卫、token 过期、跨域、打包部署这类实际问题时,需要一份可以直接照着做的排查手册。我会按照项目从创建到上线的实际推进顺序来讲,把搜到的热词全部串进去,顺便把我实测中踩过的坑也一并写出来。
1. 环境准备与项目初始化,这一步决定了后面八成的问题
1.1 Node.js 安装与版本选择的几个关键细节
不管你是要用 Vue CLI 还是最新的 Vite,第一步永远是装 Node.js。很多初学者随手装一个最新版,然后发现某些老项目跑不起来,或者装了半天依赖总是报错,八成就是 Node 版本不对。
目前实际情况是:Vue 2 项目搭配 Node 14 到 16 比较稳妥,Vue 3 项目推荐 Node 16 以上,如果要用 Vite 5 或更新版本,Node 版本需要 18+。用node -v和npm -v检查一下自己的版本,如果版本不对,不用卸载重装,直接装一个 nvm(Node Version Manager)来管理多版本。切换命令很简单:nvm install 16.20.2,然后nvm use 16.20.2。
依赖安装慢是另一个高频问题。国内直接用npm install往往会卡很久,建议全局切换到镜像源,执行一次npm config set registry https://registry.npmmirror.com,后面所有项目的下载速度都会有质的提升。装了 pnpm 的话,pnpm 也可以走这个镜像,统一在.npmrc里写registry=https://registry.npmmirror.com即可。
注意:不要用 cnpm 安装依赖。cnpm 有时候会改变依赖的目录结构,导致后续某依赖找不着一路报错,排查起来非常费劲。镜像源可以换,包管理器建议老老实实用 npm 或 pnpm。
1.2 从 Vue CLI 到 Vite:项目创建方式的变化
以前我们创建一个 Vue 项目,默认是vue create my-project,用的是 Vue CLI,底层基于 Webpack。现在官方推荐的是 Vite,创建命令是:
npm create vue@latest注意这里创建的是 Vue 3 项目,过程中会询问你是否需要 TypeScript、Vue Router、Pinia、ESLint 等,推荐全部选上,后面写代码时这些内置配置能省很多事。
如果是公司已有老项目,还在用 Vue CLI,也不要急着迁移。CLI 的成熟生态和 Webpack 的兼容性在维护老项目时反而是优势。但任何新项目建议直接 Vite,开发服务器启动速度快到一个离谱的程度,热更新也几乎是秒级响应。
创建完项目后,进入目录安装依赖:
cd my-project npm install npm run dev看到Local: http://localhost:5173/这样的输出就说明项目已经跑起来了。
1.3 IDE 与 Vue 官方插件的坑
写 Vue 3 项目时,Volar 已经是事实上的标准插件。如果之前用过 Vetur,建议在 Vue 3 项目里把它禁用,否则两个插件同时生效会产生一些奇怪的代码提示和类型报错。
Volar 安装后,第一步把它的 Takeover Mode(接管模式)打开:在 VS Code 里按Ctrl+Shift+P,输入Vue: Switch to Takeover Mode。这样 TS 和 Vue 的文件都由 Volar 接管,类型提示和 node_modules 里的解析会更快更准。
此外,Typescript 的 vue 文件诊断有时会误报,遇到这种情况在设置里搜索vue.server.hybridMode,如果开着就临时关掉试试,多数能解决。
2. 路由设计:从基本传参到完整鉴权链路
2.1 动态路由传参的各种姿势
Vue Router 4 是 Vue 3 项目的标配,路由传递参数的方法大概有三种:路径参数、查询参数(query)、以及通过路由元信息(meta)附带数据。
路径参数的定义方式:
{ path: '/detail/:id', name: 'Detail', component: () => import('../views/Detail.vue') }跳转时有两种写法:
// 字符串方式 router.push('/detail/1024') // 对象方式(更适合传多个参数) router.push({ name: 'Detail', params: { id: 1024 } })接收参数时,在模板里用$route.params.id,在组合式 API 里用useRoute()后route.params.id读取。
query 参数比较适合传一些可选的筛选条件,比如列表页跳转时保留搜索关键词:
router.push({ path: '/list', query: { page: 2, keyword: 'vue' } }) // 读取 route.query.page还有一个容易被忽略的细节:params不能和path混用。必须使用name加params的组合方式,因为path会被直接作为字符串匹配路径,/detail/1024这种场景用路径参数显然更合理,而query天然支持任意 URL 拼接。
动态路由也需要注意组件复用问题。同一个组件实例在/detail/1和/detail/2之间切换时,因为组件被复用,onMounted并不会重新触发。正确做法是监听route.params.id的变化重新拉取数据:
watch(() => route.params.id, (newId) => { fetchDetail(newId) })或者干脆给 router-view 加一个:key="route.fullPath",强制每次路由变化都重新渲染组件。
2.2 路由拦截器与登录态校验,前后端分离的第一道门
路由拦截器做的事情很简单:在跳转页面之前,判断用户有没有权限访问。最常见的场景就是未登录用户想进入个人中心时,自动跳转到登录页。
Vue Router 4 的全局前置守卫写法:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path === '/login') { next() return } if (to.meta.requiresAuth && !token) { next('/login') return } next() })注意:
next()在上面的代码里已经返回了,所以后面不会再有多余逻辑。新手常见的误区是每个分支都不 return,最后next()又被执行一次,造成重复导航的警告。
路由元信息(meta)里可以配置requiresAuth、title、roles这些字段,从而在守卫里做更细的权限判断。如果有“登录后回跳到原页面”的需求,可以在守卫里把目标路由存一下:
if (!token) { next({ path: '/login', query: { redirect: to.fullPath } }) }登录成功后再判断route.query.redirect,有值就跳过去,没有就进首页。
2.3 路由懒加载与打包性能之间的平衡
Vue CLI 和 Vite 都支持路由级别的代码分割。写法很简单,组件引入改成动态 import:
// 以前 import Home from '../views/Home.vue' // 现在 const Home = () => import('../views/Home.vue')这样打包时每个页面会被拆分成单独的 chunk,首屏只加载当前页面需要的 JS,大大减少初次加载时间。
但也要注意别过度拆分。如果某个页面极小,比如就一个简单的约 10KB 的关于页,也单独拆成一个 chunk,反而会增加请求数。实际情况中,把基础组件和工具函数放 vendor 里,按业务模块划分 chunk,是性价比最高的方案。
3. 状态管理:Vuex 和 Pinia 到底该怎么选
3.1 Pinia 比 Vuex 强在哪
写 Vue 3 项目,状态管理这一环大家纠结最多的就是选 Pinia 还是 Vuex 了。官方给出的态度已经很明确:Pinia 是新的推荐选择。这不是说 Vuex 被抛弃了,老项目继续用 Vuex 没有任何问题,但新项目直接用 Pinia 就好。
Pinia 的优势主要体现在几个方面:
Pinia 完全基于组合式 API 设计,把 store 的写法强行和各种模块结构掰开,写起来更像一个普通的组合式函数。Vuex 4 为了兼容 Vue 3,本质上是在 Vuex 3 基础上补的类型适配,设计思路还是旧的选项式风格。
Pinia 没有 mutations 的概念。Vuex 里要改 state,必须先定义 mutation,异步操作还要再套一层 action,一个简单的计数功能得写三处代码。Pinia 直接允许你在 action 里同步或异步修改 state,代码量直接减半。
Pinia 天然支持 TypeScript,不需要像 Vuex 那样额外写一堆复杂的类型推断工具。
一个简单的 Pinia Store 长这样:
// stores/counter.js import { defineStore } from 'pinia' export const useCounterStore = defineStore('counter', () => { const count = ref(0) function increment() { count.value++ } return { count, increment } })组件里使用:
import { useCounterStore } from '@/stores/counter' const counter = useCounterStore() counter.increment()完全没有mapState、mapMutations那些让人头大的辅助函数。
3.2 从 Vuex 迁移到 Pinia 的平滑路径
如果手头 Vuex 项目想要迁移,并不需要一次性全改。Pinia 可以和 Vuex 并存,两个都注册到应用里,慢慢把 Vuex 的 store 一个一个迁移到 Pinia,等全部迁移完再移除 Vuex 依赖。
迁移时的映射关系很简单:
Vuex 的state-> Pinia 的ref或reactiveVuex 的getters-> Pinia 的computedVuex 的mutations/actions-> Pinia 的function
一个是 Vuex 写法的典型示例,一个是 Pinia 的对应写法:
// Vuex 写法 mutations: { setUser(state, user) { state.user = user } }, actions: { async login({ commit }, payload) { const user = await api.login(payload) commit('setUser', user) } } // Pinia 写法 state: () => ({ user: null }), actions: { async login(payload) { this.user = await api.login(payload) } }直接这样写就行,不需要 commit。这么一对比,抽象成本低了很多。
4. 核心功能实战:m3u8 视频播放、表格导出与地图接入
4.1 m3u8 视频播放方案选型与踩坑记录
视频流播放是后台管理系统里比较常见的需求,尤其是直播回放和教育培训类项目。m3u8 是 HLS 协议下的视频索引文件格式,浏览器原生不支持直接播放,需要借助 hls.js 或 video.js 这类库。
推荐直接用 hls.js,轻量、可控性强,支持 MSE 分片播放。如果项目里已经在用 video.js,也可以选择 video.js 的 contrib-hls 插件,但 hls.js 的更新频率和性能表现明显更好,而且和视频 UI 框架无关,非常灵活。
安装:
npm install hls.jsVue 组件中的标准接入方式:
<template> <video ref="videoRef" controls playsinline></video> </template> <script setup> import { ref, onMounted, onBeforeUnmount } from 'vue' import Hls from 'hls.js' const videoRef = ref(null) let hls = null onMounted(() => { const video = videoRef.value const videoUrl = 'https://example.com/live/master.m3u8' if (Hls.isSupported()) { hls = new Hls({ enableWorker: true, lowLatencyMode: false }) hls.loadSource(videoUrl) hls.attachMedia(video) hls.on(Hls.Events.MANIFEST_PARSED, () => { video.play().catch(() => {}) }) } else if (video.canPlayType('application/vnd.apple.mpegurl')) { // Safari 原生支持 HLS video.src = videoUrl } }) onBeforeUnmount(() => { if (hls) { hls.destroy() } }) </script>踩过的坑主要有几个:
如果直接用video.src = videoUrl赋值给 video 标签,在 Chrome 上基本播不了,必须通过 hls.js 转成 MSE 分片数据流。
hls.js 在处理多个码率时,默认会自动切换码率,但如果直播流不稳定,频繁切换会导致花屏或卡顿。建议设置startLevel: -1配合autoStartLoad: false,手动控制码率切换时机。
服务端需要开启 CORS 跨域访问,否则视频请求会直接被浏览器拦截。nginx 里配置add_header Access-Control-Allow-Origin *;是最省事的方案。
4.2 多表格数据合并导出为一个 Excel
后台管理页面里表格导出是高频需求,尤其是导出时要勾选多个表格的数据。如果直接用后端接口导出,通常会受数据量限制,而且每张表都要新写一个导出接口。前端通过 SheetJS(xlsx)配合 file-saver 来做,完全不吃后端资源。
安装依赖:
npm install xlsx file-saver多表合并导出的核心思路是:每个 sheet 对应一张表格的数据,SheetJS 的utils.json_to_sheet可以把 JSON 数组直接转成工作表结构,再通过utils.book_append_sheet加到同一个工作簿里。
import * as XLSX from 'xlsx' import { saveAs } from 'file-saver' function exportMultipleSheets(dataList, sheetNames, fileName = 'export.xlsx') { const wb = XLSX.utils.book_new() dataList.forEach((data, index) => { const ws = XLSX.utils.json_to_sheet(data, { header: ['id', 'name', 'createTime', 'status'] }) // 设置每列宽度,中文表头时这个细节很影响体验 ws['!cols'] = [{ wch: 10 }, { wch: 20 }, { wch: 24 }, { wch: 12 }] XLSX.utils.book_append_sheet(wb, ws, sheetNames[index]) }) const excelBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }) const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }) saveAs(blob, fileName) }坑点提醒: SheetJS 默认的中文列名没问题,但如果数据结构里包含Date对象或者特殊字符,导出时可能出现乱码或解析异常。稳妥做法是把所有字段先统一转成字符串,再导入导出。
4.3 在 Vue 里集成腾讯地图
地图组件在项目里通常用于展示设备位置、配送轨迹、门店分布等。腾讯地图支持 Vue 直接通过 script 标签加载,不需要额外封装成 npm 包。
申请好腾讯地图开发者 key 后,在index.html里引入:
<script src="https://map.qq.com/api/gljs?v=1.exp&key=YOUR_KEY"></script>然后在组件里初始化:
<script setup> import { ref, onMounted, onBeforeUnmount } from 'vue' const mapContainer = ref(null) let map = null onMounted(() => { map = new window.TMap.Map(mapContainer.value, { center: new window.TMap.LatLng(39.908860, 116.397390), zoom: 12 }) // 添加标记 const marker = new window.TMap.Marker({ position: new window.TMap.LatLng(39.908860, 116.397390), map: map }) }) onBeforeUnmount(() => { map = null }) </script> <template> <div ref="mapContainer" style="width: 100%; height: 400px;"></div> </template>注意地图容器必须有明确的宽高,否则地图区域会是灰色。另外要确保初始化发生在onMounted之后,因为此时 DOM 已经渲染完成,容器尺寸已经计算出来了。如果使用了v-if来控制地图容器的显示,务必在 DOM 真正显示后再初始化,否则需要手动调用map.getContainer()或是对应地图实例的resize方法重新计算尺寸。
4.4 WebSocket 推送与 vue 项目里的接入姿势
WebSocket 在后台管理系统里常用于订单提醒、消息推送、协同编辑和大屏数据实时刷新。
在 Vue 3 里接入 WebSocket,建议封装一个全局的 socket 管理模块,而不是每个页面各自 new 一个 WebSocket,否则连接数会随着页面切换疯狂增长。
// utils/socket.js class SocketService { constructor(url) { this.url = url this.ws = null } connect() { this.ws = new WebSocket(this.url) this.ws.onopen = () => { console.log('socket connected') } this.ws.onmessage = (event) => { const data = JSON.parse(event.data) // 根据消息类型分发到具体页面 this.handleMessage(data) } this.ws.onclose = () => { // 断线重连 setTimeout(() => this.connect(), 3000) } } handleMessage(data) { // 用事件总线或 pinia 进行状态更新 } } export default new SocketService()像这种长时间连接的功能,断线重连是必须做的。在onclose里加延时重连,同时注意心跳机制,每 30 秒发送一次自定义心跳包,服务端收到后返回 pong,如果连续几次收不到 pong 就主动断开重建连接,避免“假连接”导致推送丢失。
5. 前后端分离协作:Token 处理、跨域与部署
5.1 请求封装与 axios 拦截器里 token 的完整处理
Spring Boot + Vue 前后端分离已经是目前最普遍的团队协作模式。Vue 负责页面渲染和交互,Spring Boot 只提供 RESTful API。两者之间通过 HTTP 请求通信,这个过程中 Token 处理是最容易出问题的环节。
Token 的存储位置,我推荐放localStorage。虽然有些安全团队建议放内存变量,但刷新页面会丢失,需要重新登录,实际体验不好。localStorage 的 XSS 风险可以通过内容安全策略(CSP)和输入校验来缓解,算是便利性和安全性的一个折中方案。
封装 axios 时的核心逻辑:
// utils/request.js import axios from 'axios' import router from '@/router' const service = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:附加 token service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, error => Promise.reject(error)) // 响应拦截器:统一处理错误码和 token 过期 service.interceptors.response.use( response => { const res = response.data if (res.code !== 0) { // 业务错误统一弹提示 return Promise.reject(new Error(res.message)) } return res.data }, error => { if (error.response?.status === 401) { localStorage.removeItem('token') router.push('/login') } return Promise.reject(error) } )一个关键点是:token 过期返回 401 后的处理逻辑。简单跳转登录页是最初级的做法。实际项目里,遇到这种情况可以考虑在请求拦截器里做“单点故障升级”处理,即用一个isRefreshing标识,把并发请求都挂起,先刷新 token,再重新发请求。这样一个 token 过期不会导致多个请求同时跳到登录页,用户体验会好很多。
5.2 Vite 和 Nginx 的跨域配置
开发环境下的跨域问题,Vite 通过 proxy 就能解决。在vite.config.js里配置:
export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, // 如果需要后端接口路径不带 /api,可以重写 rewrite: path => path.replace(/^\/api/, '') } } } })这样配置后,前端请求/api/login,开发服务器会把它转发到http://localhost:8080/login,同时changeOrigin: true会把请求头里的 Host 改成目标地址,后端不会出现跨域问题。
生产环境的跨域主要交给 Nginx 处理:
location /api/ { proxy_pass http://localhost:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }5.3 打包后布局异常的排查思路
打包后布局异常是一个很经典的问题,特别是 Vue 项目部署到服务器上刷新页面以后 404,或者样式一加载就错乱,CSS 引用路径完全不对。
主要原因通常有三个:
源码里用了绝对路径引用静态资源。Vue CLI 打包后 index.html 引用 CSS/JS 的路径默认是/js/app.xxx.js,如果项目部署在子目录(比如http://example.com/admin/),这个/js/...会直接指向域名根目录,资源自然找不到。解决方法是在vue.config.js里配置publicPath: './',用相对路径,或者在构建部署时通过--base=./指定。
Vite 项目则需要在vite.config.js里设置:
export default defineConfig({ base: './' })另一个是前端路由用了 history 模式,Nginx 没有配置try_files,刷新某个页面时 Nginx 按照路径去找文件找不到,直接返回 404。Nginx 配置如下:
location / { try_files $uri $uri/ /index.html; }把路由交给index.html,让前端路由接管,刷新页面就能正常加载了。
布局乱还有一种情况是:路由切换正常,但刷新后页面错位,多是因为某些 JS 在组件挂载前就尝试操作 DOM,导致初始化时序不对。排查时先打开浏览器控制台看有没有报错,再按 Network 面板里的请求顺序确认资源是否全部加载成功。
6. 项目实战方向:从开源脚手架到毕业设计的思路
6.1 若依(RuoYi)这类 Vue 开源框架怎么在 IDEA 里跑起来
前几天还有人问我“若依这种开源项目怎么在 IDEA 里部署”,这个框架在中小企业里的普及率确实高。它的结构是 Spring Boot 做后端 + Vue 做前端,两个工程独立运行,通过接口交互。
在 IDEA 里同时启动前后端项目,核心就三步:
后端工程导入 IDEA,配置好 Maven 仓库路径,等待依赖下载完成。然后修改application-druid.yml里的数据库连接信息,把 MySQL 和 Redis 都启动起来,直接运行RuoYiApplication.java主类。
前端工程用 IDEA 内置的终端执行:
npm install npm run dev因为前后端端口不同,需要在vue.config.js里配置 devServer 的 proxy 把请求转发到后端端口。
有一个小坑需要留意:若依的本地开发默认端口是 8080,如果会和前端 Vite 的代理目标端口冲突,可以在配置文件里改后端的 server.port,或者在前端 proxy 里改 target 端口,保持一致即可。
这类开源脚手架的优点是大量通用功能(用户管理、部门管理、菜单管理、代码生成)开箱即用,适合企业内部项目快速启动。但要注意版权协议和二次开发规范,改代码时尽量保持原有分层,避免后续升级依赖困难。
6.2 适合做毕业设计的 Vue 项目选题与注意点
如果是学生朋友做毕业设计,基于 Spring Boot + Vue 的选题非常多:在线点餐系统、流浪动物领养管理系统、图书借阅系统、班级数据分析平台、校园二手交易平台……
选题上我建议避开两个极端:所有同学都做的重复度太高的,和完全没有实际使用场景的。第三方评审时更看重的是“你做的东西解决了什么实际问题”。比如“流浪动物领养管理系统”比“基于 Vue 的通用后台管理系统”更容易讲清楚需求来源和业务价值。
技术选型上:前端 Vue 3 + Element Plus + Pinia + Axios 是最稳妥的一套组合;后端 Spring Boot + MyBatis-Plus + MySQL 可以快速把 CRUD 写完。如果想要答辩时有一点亮点,可以加一个 WebSocket 做实时通知,或者接入 hls.js 做宠物领养视频展示,这一加分项在论文里也很容易展开写作。
论文系统设计和实现部分,建议画清楚架构图和 E-R 图,代码不一定要多么炫酷,只要逻辑清晰、注释清楚、核心功能能跑通,答辩环节基本不会出大问题。
6.3 django + vue 这类跨技术栈组合的实践价值
有人用 FastAPI + Vue,有人用 Django + Vue,还有人用 Spring Boot + Vue。前端统一是 Vue,差异在后端技术栈。这类跨技术栈项目最大的价值,在于锻炼“约定优于配置”的能力:后端返回什么数据结构,前端如何与之对齐,接口文档怎么维护,这些在实际工作中比具体用哪个框架更重要。
拿 FastAPI + Vue 来说,FastAPI 自动生成的 Swagger 文档接口调试非常方便。前后端联调时,前端同学可以直接对着文档看字段类型,省去翻代码的麻烦。
跨技术栈协作时有一个高频冲突点:后端返回的时间字段格式、字段命名风格(下划线还是驼峰)不统一。建议在项目启动时就把接口规范定好:统一返回{ code, message, data }结构,时间统一用时间戳或YYYY-MM-DD HH:mm:ss,字段名后端统一转成驼峰,前端拿到就能直接用。
7. 调试工具与日常效率提升
7.1 Vue Devtools 插件的安装与常见问题
Vue Devtools 是调试 Vue 项目的必备插件,它可以查看组件树、路由信息、Pinia/Vuex 的实时状态、事件触发记录,比手动 console.log 的效率高得多。
Chrome 应用商店直接搜索 Vue.js devtools 安装即可。插件安装后,打开 Vue 项目页面,地址栏右侧的图标会从灰色变成 Vue 的绿色,打开开发者工具能看到 Vue 面板。
常见问题有两个:插件装上了但图标是灰色,说明当前页面不是 Vue 项目,或者项目没有以开发模式运行(生产构建会禁用 devtools);另一个是项目里开了严格模式和插件冲突,可以在控制台手动引入独立的 devtools 脚本解决。
Firefox 和 Edge 也有对应的 Vue Devtools 版本,用法一致。
7.2 Vite 构建时的 ignonred build scripts 报错怎么办
执行npm install时有时候会看到这样的提示:
ignored build scripts: esbuild@0.21.5这不是安装失败了,而是 npm 基于安全策略拦下了依赖里的构建脚本。esbuild 的 postinstall 脚本会在安装时把原生的二进制文件下载到位。如果被忽略,后面npm run dev启动时 esbuild 可能会报缺少二进制。
解决办法是在项目根目录建一个.npmrc,写入:
esbuild: true或者用命令强制重新构建:
npm rebuild esbuild这样 esbuild 的二进制就补回来了。如果还不行,直接删掉 node_modules 和 package-lock.json 重新安装一次,基本都能解决。
7.3 vue ui 自动断开问题的排查方向
Vue CLI 自带的图形化管理界面vue ui在部分环境下会出现连接自动断开的情况。常见原因一般是端口被占用,或者浏览器有缓存,导致本地服务通信中断。
排查时可以先用命令行查看端口状态,确认vue ui的默认端口 8000 没有被别的程序占用。换一个端口试试:
vue ui --port 8088如果断开的频率还是很高,考虑是不是浏览器插件的问题,用无痕模式打开http://localhost:8088再测试。
8. 常见面试题串讲:Vue 老一辈和新一代的问题都有
收集到的热词里有“vue面试题”,这块我也顺手整理一下。面试官问 Vue 相关的问题,翻来覆去其实就是那几类:响应式原理、生命周期、组件通信、虚拟 DOM、diff 算法、Vue 2 到 Vue 3 的差异。
Vue 3 的响应式基于 Proxy,而不是 Vue 2 的 Object.defineProperty。Proxy 可以直接监听对象属性的新增和删除,所以 Vue 3 里不再需要用$set来给对象添加属性,数组的索引操作也能被正常响应。
组件通信方式:父传子用 props,子传父用 emit 或 v-model,跨层级用 provide/inject,通用状态用 Pinia,复杂场景可以在组件实例上挂载 mitt 事件总线。
生命周期变化也是常考的点:beforeDestroy变成了beforeUnmount,destroyed变成了unmounted,新增了onMounted、onUpdated、onBeforeUnmount这些组合式 API 写法。还有一个坑是onMounted的执行顺序:子组件先于父组件,先设好子组件的数据,父组件才能拿到正确状态。
Vue 2 和 Vue 3 的 diff 算法差异,面试官偶尔会深挖。Vue 3 的虚拟 DOM 对动态节点做了 patch flag 标记,可以跳过静态节点的对比,性能提升比较明显。
如果是偏向日常业务的岗位,面试官还会问路由守卫、Axios 拦截器、权限控制、keep-alive 组件缓存这类的实操内容。能把这些功能和项目场景结合讲清楚,比背概念更有说服力。
9. 组件化开发与工程化进阶
9.1 Composable 组合式函数,把逻辑抽出来复用
Vue 3 的 Composable(组合式函数)解决了 Vue 2 时代 Mixin 的杂乱问题。Mixin 的缺点很明显:变量来源不透明、多个 mixin 可能有命名冲突、跨项目复用困难。
Composable 就是普通函数,只是在内部可以使用ref、reactive、computed、watch这些 Vue API。按需提取一段可复用的逻辑,返回给组件使用。
举一个实际例子:滚动加载下一页数据:
// composables/useInfiniteScroll.js import { ref, onMounted, onBeforeUnmount } from 'vue' export function useInfiniteScroll(loadMore) { const loading = ref(false) const hasMore = ref(true) function onScroll() { if (loading.value || !hasMore.value) return const distanceToBottom = document.documentElement.scrollHeight - window.innerHeight - window.scrollY if (distanceToBottom < 100) { loading.value = true loadMore().finally(() => { loading.value = false }) } } onMounted(() => window.addEventListener('scroll', onScroll)) onBeforeUnmount(() => window.removeEventListener('scroll', onScroll)) return { loading, hasMore } }组件里调用:
const { loading, hasMore } = useInfiniteScroll(fetchNextPage)逻辑被完整封装起来,组件模板里只关心渲染,维护成本低很多。
9.2 v-model 在自定义组件里的正确姿势
Vue 3 的 v-model 在自定义组件上的默认值从value改成了modelValue,这让组件双向绑定的语义更清晰。以前写自定义输入框组件:
<template> <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" /> </template> <script setup> defineProps(['modelValue']) defineEmits(['update:modelValue']) </script>在父组件里直接用v-model="keyword"就实现了双向绑定。
v-model 还支持多个绑定值,比如一个弹窗组件同时需要控制显示状态和一些操作结果:
<Dialog v-model:visible="dialogVisible" v-model:title="dialogTitle" />这个用法在做表单类组件、弹窗类组件、抽屉类组件时非常实用,比传一堆 props 再用事件回调的方式省心得多。
9.3 ant-design-vue 这类 UI 库引入时的主题定制经验
Ant Design Vue 是目前 Vue 3 生态里企业级组件库一个可靠选项。安装:
npm install ant-design-vue@4.x如果要定制主题色,可以在 Vite 项目里用 CSS 变量覆盖:
:root { --ant-primary-color: #1890ff; /* 自定义主色 */ }组件库默认样式不满足需求时,不要直接去改 node_modules 里的源码,正确的做法是全局覆盖样式类,或者用 CSS Modules 给组件包裹一层自己的类名进行样式隔离。
还有一个实用技巧:按需引入组件可以减少打包体积。安装unplugin-vue-components和unplugin-auto-import这两个 Vite 插件后,组件可以自动按需加载,同时在模板里使用a-button这类前缀的组件时,不需要手动注册。配合 ant-design-vue 的图标,打包后体积能明显减小。
9.4 多表格导出一个 Excel 的升级版:样式美化
前面写过多 sheet 导出的基础方法,如果再进阶一步,需要在导出的 Excel 里带上标题行、合并单元格和颜色样式。SheetJS 的能力偏基础,真正完整的样式需要借助exceljs这个库。
npm install exceljs用 exceljs 创建带样式的表头:
import ExcelJS from 'exceljs' const workbook = new ExcelJS.Workbook() const sheet = workbook.addWorksheet('Sheet1') sheet.columns = [ { header: 'ID', key: 'id', width: 10 }, { header: '姓名', key: 'name', width: 20 }, { header: '创建时间', key: 'createTime', width: 24 } ] sheet.getRow(1).font = { bold: true, color: { argb: 'FFFFFF' } } sheet.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: '4472C4' } }exceljs 支持合并单元格、边框、条件格式、公式、图片,做财务报表导出时性能也稳定,推荐在复杂场景下直接用。
10. 一点个人经验总结(关于整个 Vue 生态的学习路径)
写到这里,再回头看你搜到的那一串关键词,其实它们已经悄悄把 Vue 学习路径画出来了。
先装环境(nodejs 安装、依赖安装、vscode 配置),然后把 Vue 项目跑起来;接着了解模板语法和响应式;进入项目之后,配合路由和后端做完整应用,组件化思维在这个过程中自然被锻炼出来。
真正把多个技术点串联起来的关键,是动手做一个完整的项目,而不是只看教程。我在实际使用中发现,把 m3u8 播放、Excel 导出、地图标记、WebSocket 推送这些都塞进同一个 demo 项目里,跑通一遍以后,面试时不管被问到哪一块,心里都有底。
最后再分享一个小技巧:Vue 的报错信息其实写得非常有条理,遇到问题先别慌,把控制台红字按顺序读一遍,八成问题就出在离报错位置最近的上一次操作上。这个习惯能帮你节省大量查错时间。