我一直觉得,Vue 项目的搭建是很多前端新人遇到的第一个"劝退点"。不是 Vue 本身难,而是从安装 Node.js、选脚手架、配路由、调代理到最终打包上线,中间每一步都可能冒出个报错把人卡住。你问"如何搭建一个vue项目",网上答案一大堆,但要么只讲一半,要么直接甩个命令让你复制,根本不知道背后发生了什么。
这篇文章我想换个方式,不只是在命令行里敲几条命令就完事,而是把从零到能上线一个 Vue 项目的完整链路走一遍,包括环境准备、脚手架选型、项目结构、路由配置、状态管理、HTTP 请求封装、打包优化以及常见的坑。目标只有一个:你照着做,能跑通;你理解了为什么,换个项目也能自己搞定。
内容适合正在学 Vue 的初学者、准备用 Vue 做毕业设计或外包项目的同学,以及想从前端切到全栈、需要自己从零搭项目的开发者。当然,如果你已经能熟练用 Vite 创建项目,这篇里的坑和原理部分也值得扫一眼,很多细节是平时文档里不会写的。
1. 动手之前,先把 Vue 项目的"地基"打好
很多人一上来就敲npm create vue@latest,结果连 Node.js 都没装对版本,装依赖的时候报一堆错。这个阶段最值得花时间,因为环境问题会伴随你整个开发周期。
1.1 你需要准备哪些开发环境
一个标准的 Vue 项目,开发环境核心就三样:Node.js、npm(或 pnpm/yarn)、一个趁手的编辑器。
Node.js 是 Vue 项目的地基,npm 是包管理工具,负责下载和管理项目依赖。安装 Node.js 的坑主要有两个:版本过高或过低,以及下载源导致依赖安装慢。
推荐用 LTS(长期支持)版本,不要追最新版。Vue 3 + Vite 这套组合在 Node 18 以上的 LTS 版本都跑得很稳。我自己踩过的坑是,在 Node 16 上跑 Vite 5 的项目,启动直接报Error: error:0308010C:digital envelope routines::unsupported,这就是 Node 版本和 Vite 版本不兼容造成的,升级到 Node 18 后问题消失。
编辑器首选 VSCode,装两个扩展就够用:Vue 官方推荐的 Vue Language Features (Volar)以及 TypeScript Vue Plugin。如果你用的是 Vue 2,才需要装 Vetur,Vue 3 项目里装了 Vetur 反而会干扰提示,建议禁用。
还有一个小细节,很多人会忽略:把 npm 的 registry 切换到国内镜像。不然下载node_modules的时候,一个几十 MB 的依赖在国外源上下半小时,体验非常差。切换方式是命令行执行npm config set registry https://registry.npmmirror.com,换完之后用npm config get registry验证。
注意:不要用 cnpm 代替 npm。cnpm 偶尔会出现依赖目录结构不对的问题,导致某些包运行时找不到文件。用官方 npm 加国内镜像最有保障。
1.2 为什么我建议新项目优先考虑 Vite 而不是 Webpack
Vue 官方在 2022 年之后,新项目的默认推荐就是 Vite。这个选型的核心原因只有一个:快。
Webpack 启动一个大型项目的开发服务器,冷启动常常要等 30 秒到几分钟,改一行代码刷新一次也要一两秒。而 Vite 利用了浏览器原生 ES Module 能力,开发服务器启动时不用打包整个项目,按需编译,冷启动秒级完成,热更新基本在几百毫秒以内。
Vite 在开发阶段快得多的原理,用一句话解释:Webpack 是把所有模块先打包成一个 bundle 再给浏览器执行;Vite 是在浏览器请求某个模块时,服务端实时把那个模块编译好返回,请求到哪里编译到哪里。
生产构建方面,Vite 默认用 Rollup 做打包,产物比 Webpack 时代的优化配置更好用。不过有两点要提前说明:Vite 只支持 ES Module 构建,旧浏览器需要额外插件处理;另外如果你的项目用了大量老第三方库,可能要找兼容方案。但 2025 年的今天,大多数情况都不太存在这种兼容问题了。
1.3 脚手架二选一:create-vue 还是 Vue CLI
Vue 官方脚手架有两个:create-vue(新一代)和@vue/cli(老一代)。建议新项目无脑选create-vue。
create-vue是基于 Vite + Vue 3 + 组合式 API 的官方方案,支持 TypeScript、JSX、Router、Pinia、Vitest、ESLint、Prettier 等功能的交互式选择。它是当前 Vue 生态最主流、最正规的起点。
@vue/cli已经进入维护模式,不再推荐用来创建新项目。如果你的课程设计、公司老项目必须用 Vue CLI,我也在最后加了一小节说明怎么创建和启动,方便你了解两者差异。
2. 创建项目实操:从命令行到看到第一个页面
这一章我们直接上手。拿create-vue为例,从输入命令到浏览器出现页面,整个过程大概 5 分钟内完成。
2.1 用 create-vue 创建项目,每一步选什么
首先打开命令行,进入你想存放项目的目录,执行下面这行命令:
npm create vue@latest执行后,命令行会问你几个问题,我一个个说下选择思路:
- Project name:输入项目名称,比如
my-vue-app。注意 npm 包名不能有大写字母,建议全部小写加短横线。 - Add TypeScript?:如果你刚入门,选 No 可以少碰类型报错;如果做正式项目,强烈建议选 Yes,Vue 3 + TS 是趋势。
- Add JSX Support?:默认 No 即可,Vue 单文件组件模板足够日常使用,JSX 不是必选项。
- Add Vue Router?:选 Yes。单页应用基本都要路由。
- Add Pinia?:选 Yes。状态管理后面一定会用到。
- Add Vitest?:选 No。单元测试框架,需要测试的项目再加不迟。
- Add End-to-End Testing Solution?:选 No。端到端测试用 Playwright 或 Cypress,按需添加。
- Add ESLint and Prettier?:建议 Yes,代码规范可以靠它自动约束。
回答完这些,脚手架会自动把基础项目生成到目录。接着执行:
cd my-vue-app npm install npm run dev默认情况下开发服务器跑在http://localhost:5173,浏览器打开就可以看到 Vue 官方欢迎页。
2.2 项目目录结构逐层拆解
脚手架生成的项目目录长这样:
my-vue-app/ ├── node_modules/ # 依赖包,npm install 生成的 ├── public/ # 公共静态资源,打包时原样复制到根目录 ├── src/ │ ├── assets/ # 构建时处理的静态资源(图片、样式) │ ├── components/ # 公共组件 │ ├── router/ # 路由配置 │ ├── stores/ # Pinia 状态管理 │ ├── views/ # 页面级组件 │ ├── App.vue # 根组件 │ └── main.ts # 入口文件 ├── index.html # HTML 模板,Vite 的入口 ├── package.json # 项目元数据、依赖声明和脚本 └── vite.config.ts # Vite 配置文件重点理解几个文件的作用:
main.ts是应用的入口,它做三件事:创建应用实例、注册插件(路由、状态管理等)、挂载到页面上的某个 DOM 节点。代码大致长这样:
import { createApp } from 'vue' import { createPinia } from 'pinia' import router from './router' import App from './App.vue' const app = createApp(App) app.use(createPinia()) app.use(router) app.mount('#app')App.vue是根组件,所有页面最终都渲染在里面。router-view是路由出口,URL 变化时会自动切换在这个位置渲染的组件。
index.html是整个应用的 HTML 外壳,它的body里面只有一个<div id="app"></div>,Vue 实例会挂载到这个 div 上。Vite 开发时以它为入口,构建时注入打包好的 JS/CSS。
2.3 启动开发服务器,验证项目能跑起来
启动开发服务器命令是npm run dev。看到终端出现Local: http://localhost:5173/就说明成功了。浏览器打开后,修改src/views/HomeView.vue里的模板内容,保存,页面应该会即时更新。
开发服务器的核心优势是热模块替换(Hot Module Replacement,简称 HMR),修改代码后浏览器不用整页刷新,组件状态还能保留。Vite 的 HMR 速度在大型项目里优势更明显。
这里提前说一个常见问题:如果端口 5173 被占用,Vite 会自动往后面的端口尝试,终端会提示新端口。这个不是 bug,不要慌。
3. 项目骨架搭建:路由、状态管理、HTTP 请求
项目能跑起来只是第一步,一个真正的项目还需要搭建三条基础设施:路由负责页面跳转,状态管理负责跨组件共享数据,HTTP 请求封装负责与后端通信用。这三件套搭好后,业务代码就能往上面填了。
3.1 配置路由:基础路由、动态路由与拦截器
Vue Router 4 是 Vue 3 的官方路由。在src/router/index.ts里配置路由表,核心代码如下:
import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', name: 'home', component: () => import('@/views/HomeView.vue') }, { path: '/about/:id', name: 'about', component: () => import('@/views/AboutView.vue') } ] }) export default router三个关键概念说一下:
createWebHistory是 HTML5 History 模式,URL 看起来是http://localhost:5173/about,干净美观。还有一种createWebHashHistory是 hash 模式,URL 里带#。History 模式在开发环境一切正常,但打包部署到 Nginx 等服务器时,如果没配置 fallback,刷新二级页面会 404。这点在「5. 打包上线」部分细说。
动态路由:/about/:id中的:id是路由参数,组件里用route.params.id获取。页面跳转用router.push({ name: 'about', params: { id: 123 } }),这样代码里不用硬编码 URL。
路由拦截器是很多业务场景的必需品,最典型的是登录鉴权。在访问需要登录的页面时,如果未登录,跳到登录页;已登录才能继续。实现方式是在路由配置里加一个meta字段,然后调用router.beforeEach做全局守卫:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { next({ name: 'login' }) } else { next() } })这个逻辑不复杂,但注意判断顺序:没有requiresAuth标记的公共页面应该直接放行,否则会影响正常访问。
3.2 状态管理:Pinia 还是 Vuex
如果你看的热搜词里出现了"vue pinia vs vuex",说明你在关注状态管理的选型。结论简单直接:新项目用 Pinia,老项目才看 Vuex。
Pinia 是 Vue 官方在 Vuex 之后推出的新状态管理库,创建项目时直接可勾选集成。对比 Vuex 4,Pinia 的优势很直观:
| 对比维度 | Pinia | Vuex |
|---|---|---|
| 学习成本 | 低,接近直接用对象 | 高,需要理解 state/mutations/actions 概念 |
| TypeScript 支持 | 原生好 | 需要写额外类型代码 |
| 代码量 | 少 | 多 |
| 官方维护 | 活跃 | 维护模式 |
Pinia 的 stores 定义简单多了,在src/stores/counter.ts里这样写:
import { defineStore } from 'pinia' import { ref } from 'vue' export const useCounterStore = defineStore('counter', () => { const count = ref(0) function increment() { count.value++ } return { count, increment } })组件里使用:
<script setup lang="ts"> import { useCounterStore } from '@/stores/counter' const counter = useCounterStore() </script> <template> <button @click="counter.increment()">{{ counter.count }}</button> </template>这种 Setup 风格写法跟写组合式函数很像,非常直观。
3.3 封装 Axios,统一处理请求和 Token
几乎每个 Vue 项目都要跟后端接口打交道。前端最主流的 HTTP 库是 Axios。直接用会产生很多重复代码,所以通常会做一层封装,把baseURL、超时时间、请求头、Token 注入和错误提示集中处理。
先在项目里安装 axios:
npm install axios在src/utils/request.ts里封装一个实例:
import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000 }) // 请求拦截器:自动携带 token request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) // 响应拦截器:统一处理业务错误和登录过期 request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res }, error => { if (error.response?.status === 401) { localStorage.removeItem('token') router.push({ name: 'login' }) } ElMessage.error(error.message || '网络错误') return Promise.reject(error) } ) export default request这套封装解决三个问题:
Token 注入:登录后后端返回 token,前端存到 localStorage 或 cookie,后续每个请求自动带上。这样就不用每个接口手动写一遍 header。
统一错误提示:后端返回的业务错误码,由响应拦截器统一弹出提示,业务代码里就不用重复写alert。
登录过期处理:请求返回 401 时,统一清除本地登录状态并跳转登录页。
注意:搜索热搜里还有个"vue前后端分离请求token处理",其实思路就是上面这套。唯一要提醒的是,token 别放在
localStorage里存敏感数据,如果项目对安全要求高,可以用 HttpOnly Cookie 方案,防止 XSS 窃取。不过这个属于进阶项,大多数内部系统用 localStorage 问题不大。
这里的ElMessage是 Element Plus 组件库的轻提示,如果你项目不用 Element Plus,换成自己的 UI 组件库提示方式即可。
4. 提升开发效率:路径别名与环境变量
项目变大以后,组件之间互相引入的路径特别容易写错。写../../../../components/xxx这种事,不仅丑而且脆,随便移动下目录结构就全挂了。这个坑可以通过配置路径别名来解决。
4.1 配置@别名,告别一长串相对路径
在vite.config.ts里配置@别名指到src目录:
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { fileURLToPath, URL } from 'node:url' export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } } })配置后,import HomeView from '@/views/HomeView.vue'就能正常工作。这一步还有一个隐藏问题:如果项目使用了 TypeScript,还需要在tsconfig.json里同步配置 paths,否则 VSCode 会飘红报找不到模块。
{ "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"] } } }4.2 环境变量:不同环境用不同配置
开发环境、测试环境、生产环境的 API 地址通常不一样。把配置写死在代码里,换环境就要改代码,非常蠢。Vite 的环境变量机制可以解决。
在项目根目录创建.env.development和.env.production:
# .env.development VITE_API_BASE_URL=/api VITE_APP_TITLE=开发环境# .env.production VITE_API_BASE_URL=https://api.example.com VITE_APP_TITLE=生产环境代码里通过import.meta.env.VITE_API_BASE_URL读取。Vite 规定只有以VITE_开头的变量才会暴露给客户端代码,其他前缀不会,这是安全设计,防止敏感配置泄漏到浏览器。
4.3 代码规范:用 ESLint + Prettier 解放双眼
利益于 create-vue 创建项目时如果勾选了 ESLint 和 Prettier,项目里已经带有完整配置。npm run lint可以检查代码,npm run format可以自动格式化。
很多新手觉得 lint 是找茬,我的建议是反过来看待它:lint 强制统一的代码风格,多人协作时最大的价值就是减少"你这个括号怎么不换行"这类完全没有意义的摩擦。格式化交给 Prettier 后,提交到 git 里的代码diff也能变得干净,review 起来舒服很多。
5. 打包上线那些坑:布局异常、路由 404、依赖报错
很多人在本地开发一切正常,一打包部署就出各种问题。我按热搜词整理了几个高频问题,逐个说原因和解决方案。
5.1 打包后布局异常的常见原因
热搜词里有"vue 打包后 布局异常",这是几乎每个 Vue 新手都会遇到的一类问题。常见的表现是:本地开发显示正常,npm run build后部署上去,样式乱了、图片裂了、路由页面空白。
核心原因大概率是资源路径写死了绝对路径。默认情况下 Vite 打包后的资源路径以/开头,如果你的应用部署在域名子路径下,比如https://example.com/my-app/,那/assets/index.js就会去域名根目录找资源,自然找不到。
解决方案:在vite.config.ts里配base:
export default defineConfig({ base: '/my-app/' // 部署到子路径时配置,根域名部署就保持默认 })另外,如果项目里直接用了<img src="/images/logo.png">这种绝对路径写法,也会出现同样问题。改成下面这种或者用assets目录引入的方式:
<img src="@/assets/logo.png">5.2 路由 history 模式刷新 404
这个问题几乎每个用了createWebHistory的项目都会踩到。开发环境没问题,打包部署到 Nginx 后,访问首页正常,一旦访问https://example.com/about并刷新,就出现 404。
原因在于:前端路由是浏览器端实现的,服务器上并没有真实的/about目录。默认 Nginx 收到请求后在磁盘找对应文件找不到,就返回 404。
解决方案是配置 Nginx 的 try_files,把所有路由请求都 fallback 到index.html:
location / { try_files $uri $uri/ /index.html; }注意:用了这个配置后,所有未知路径都会返回
index.html,就不会出现后端接口路径也被 history fallback 的情况。但前提是接口 API 路径和前端路由是不同前缀,比如/api的请求单独设置 location 不走 fallback。
如果你用的是宝塔面板这类图形化管理工具,可以在网站配置里找到伪静态设置的入口,加上上面的规则即可。
5.3 依赖安装报错处理
热搜词里有 "ignored build scripts" 之类的问题,其实常见报错场景还有这些:
cpu-features、esbuild、ssh2这类包在安装时提示 "ignored build scripts",是 npm 6.4 之后为了安全引入的机制,不会影响正常使用。真正常见的坑是下面这些。
node_modules装到一半报ENOSPC指磁盘空间不足;报EACCES是权限问题,macOS/Linux 下在命令前加sudo,但更推荐用 nvm 装 Node 以避免这种情况;报ERESOLVE unable to resolve dependency tree是依赖版本冲突,最优先看是不是 npm 版本太老,或尝试删掉node_modules和package-lock.json后重新安装。
遇到依赖报错,我推荐的排查顺序是:
- 看报错关键字,判断是权限、网络还是版本问题
- 确认 Node.js 版本,
node -v查看 - 删掉
node_modules和package-lock.json,重新npm install - 换用
npm cache clean --force清理缓存后重试 - 最后再考虑是不是某个第三方包本身有问题,查 GitHub Issues
注意:不要一上来就删除
package-lock.json。这个文件锁定了依赖版本,如果团队合作时删掉它,别人npm install可能装出不同版本导致行为不一致。只有在确认是依赖树冲突且其他方式无效时才考虑删。
6. 高频问题排查速查表
以下是基于热搜和常见社区提问整理的排查速查表,方便按图索骥:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
npm run dev启动报错 | Node 版本过老或过新 | 用 nvm 切换到 Node 18 以上 LTS 版本 |
| 页面样式完全没加载 | CSS 资源引用路径不对 | 检查vite.config.ts中的 base 配置 |
| 图片裂掉 | public 目录下绝对路径引用 | 改用@/assets/或相对路径引入 |
| 路由刷新 404 | 使用了 history 模式但服务器未配置 fallback | Nginx 配置 try_files 规则 |
| 请求后端接口跨域 | 前后端不同源 | 开发环境配 Vite proxy,生产环境由后端配 CORS |
| 页面加载白屏,控制台报 JS 错误 | 可能是部署后 JS 路径 404 或语法兼容问题 | 检查网络请求资源路径,确认 base 配置和浏览器兼容目标 |
| 依赖装完后项目跑起来报模块不存在 | node_modules 损坏 | 删除 node_modules,重新 npm install |
| 热更新失效,改代码页面不刷新 | Vite HMR 遇上未处理大文件或特定插件 | 重启 dev server 试试,偶尔有用 |
| 打包后首屏加载过大 | 没有做代码分割 | 路由懒加载、按需引入 UI 组件库,可用 Vite 构建分析插件确认 |
| 多个文件格式转换报错 | 第三方库只在浏览器环境可用 | 查找该库的 SSR/构建兼容说明,考虑动态 import |
这个表格我尽量挑的都是高频真实问题,不是概念问题。每条都是我在项目或教学里亲眼看到过的。
7. 老项目怎么办:Vue CLI 项目创建与迁移思路
如果公司或毕设题目规定用 Vue CLI,或者手上有一个 Vue 2 老项目要维护,还是需要知道怎么操作。
创建方式:
npm install -g @vue/cli vue create my-vue-cli-appvue create同样有交互式选项,创建后npm run serve启动开发服务器,npm run build打包。Vue CLI 默认走 Webpack,因为配置复杂,vue.config.js 里可以自定义打包配置。
我个人的实际建议是:如果是从零开始的个人项目,别再用 Vue CLI 了。长期看 Vite 的生态和性能优势会更明显。老项目迁移也不建议一步到位重写,可以渐进式地在 Vite 环境里跑老代码,官方有专门的迁移指南,难点主要在 Webpack 插件换成 Vite 插件、公共路径和构建产物差异调整。
8. Vue 生态拓展:每个项目都少不了的"周边"
把项目跑通只算完成了骨架,真实业务场景里还有一堆需求必须面对,单独列一节说。
8.1 组件库与 UI 方案
Element Plus 在 PC 端中后台项目里用的人最多,组件全、文档好、社区大。如果你的项目偏移动端,可以用 Vant;如果追求动手能力的提升,也可以自己封装基础组件。组件库引入了之后,建议在main.ts里使用按需引入,不要app.use(ElementPlus)一次性全量引入,这会极大增加打包体积。
8.2 视频播放:Vue 配合 m3u8 流媒体
热搜词里反复出现 "vue播放m3u8",很多人以为要在 Vue 里自己写播放逻辑。其实方案很简单:m3u8 是 HLS 流媒体协议的视频文件格式,浏览器原生不直接支持,需要引入 hls.js 或使用 video.js 加 hls 插件。在 Vue 项目里,封装一个HlsPlayer.vue组件是常见做法。
思路大致是:组件内部创建一个<video>元素,用 hls.js 绑定播放地址,销毁时调用hls.destroy()防止内存泄漏。watch 到src变化时重新加载播放器。
这个需求常见于监控系统、课程点播、大屏展示,配xgplayer这类播放器会更快。
8.3 地图与可视化:Vue 里的地图组件
在一些项目中需要接入地图(腾讯地图、高德地图、百度地图),如果用 Vue 生态,推荐vue-baidu-map或官方地图 SDK 的 JS API 手动封装。如果是腾讯地图,也有对应的 Vue 组件封装思路。
地图组件容易踩的坑主要是 key 鉴权、地图容器尺寸(需要给容器固定高度)以及初始化时机(要在页面渲染完成后再初始化地图,用onMounted)。
可视化方面,如果要做配电工艺图、矩阵树图这类数据可视化业务,ECharts 加上 Vue 封装的vue-echarts是最常用的方案。ECharts 没有组件化,直接绑定到一个 div 上,注意要在onMounted或等数据返回后初始化。
8.4 数据表格导出:多表格导出 Excel
"vue 多个表格导出一个 excel" 这个需求在实际项目里特别常见,比如一个账期有多张账单,希望一次导出成一个 Excel 文件,每个 sheet 对应一张表。方案上通常用xlsx或exceljs库。
思路是:先用xlsx.utils.json_to_sheet把每个表格的数据转成 sheet,再XLSX.utils.book_append_sheet把多个 sheet 加进同一个 workbook,最后XLSX.writeFile写入文件。要注意文件名称需要有.xlsx后缀,以及中文表头需要自己在数据层面映射好。
8.5 Vue 和 Spring Boot:前后端分离的黄金组合
热搜里那块 "springboot vue前后端分离" 是很典型的企业级技术组合。Vue 负责前端页面和交互,Spring Boot 负责后端接口,通过 HTTP 请求通信。
开发阶段用 Vite 的 proxy 代理解决跨域:
export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })这个配置的意思是:前端发的/api/xxx请求,代理到后端http://localhost:8080/api/xxx,这样浏览器觉得是同源的。生产环境则通常由 Nginx 统一做反向代理,把/指向前端静态文件,/api指向后端服务。
9. 新手常见面试考点:除了搭建还会被问什么
既然热搜里有 "vue面试题",说明不少读者是边学边找工作。搭建项目之余,有几个面试高频点直接相关,这里简单梳理一下。
9.1 Angular 和 Vue 有什么区别
Angular 是完整框架,内置依赖注入、RxJS、模块体系,学习曲线陡;Vue 是渐进式框架,核心只关注视图层,路由、状态管理这些可以按需选择。Vue 的模板语法更贴合 HTML,Angular 更偏 TypeScript 和依赖注入思维。选择上:中小型项目用 Vue 更轻快,大型企业级项目 Angular 更强约束力但学习成本也更高。
9.2 路由传参的几种方式
Vue Router 传参有三种方式:query(URL 问号后面)、params(路径参数)、state(历史记录状态里)。
// query 方式 router.push({ path: '/list', query: { page: 1 } }) // params 方式,需要路由配置 path: '/detail/:id' router.push({ name: 'detail', params: { id: 2 } })query 参数刷新页面还在,params 参数如果用path跳转且路由写的是:id,也能保留在 URL 上。但如果只用name跳转且路由没有写参数,刷新就丢了。这个差别是面试常考点。
9.3 透传、Composable、v-model 这些开发细节
热搜里有几个比较进阶的词:透传、composable、v-model在组件里的用法。这里简单点一下:
透传指组件上没被声明为 props 或 emits 的属性,会自动传到组件根元素上。v-bind="$attrs"在封装组件时很有用,比如写一个自定义按钮时,用户传的disabled、style等不需要组件内声明也能生效。
Composable 是 Vue 3 组合式 API 的核心用法,把有状态的逻辑抽出来复用,比如useDebounce、useTable、usePermission,本质上就是一个返回响应式数据的函数。这套思维在写业务代码时非常能提升复用程度。
v-model在组件上的用法:子组件里defineModel()(Vue 3.4+ 语法),或者手动实现modelValueprop 和update:modelValue事件的组合。灵活使用这个,可以让表单组件双向绑定得非常顺手。
9.4 Vue 3 项目为什么要用 Volar
再补一个非常小但很细节的点:Vue 3 项目里官方推荐的 Volar 取代了 Vetur。Volar 对 TypeScript 支持更好,模板内类型检查也更完整。如果你在 VSCode 里配了 Vetur 而代码提示一直不对,果断禁用 Vetur,装回官方推荐的 Vue Language Features (Volar)。
10. 真实项目的目录规划:比脚手架默认结构更进一步
最后这部分值得单独考虑。实际业务项目里,脚手架生成的默认目录不够支撑复杂业务,需要稍作规划。
我比较常用的分层大概是:
src/ ├── api/ # 每个模块的接口请求 ├── assets/ # 静态资源 ├── components/ # 公共组件(按业务模块分子目录) ├── composables/ # 组合式函数 ├── directives/ # 自定义指令 ├── layout/ # 页面布局框架 ├── router/ # 路由配置 ├── stores/ # 全局状态 ├── styles/ # 全局样式变量 ├── utils/ # 工具函数 ├── views/ # 页面 └── types/ # TypeScript 类型定义api目录单独抽出来的原因很实际:页面组件里不要直接写request.get('/list'),而是把所有接口调用收拢到api/user.ts、api/order.ts这类文件里。后端接口路径一变,只改一个文件就行,不用全局搜。
composables是 Vue 3 组合式 API 的精华位置。比如一个表格页面常见逻辑——拉数据、loading、分页、筛选——可以抽成useTable组合函数。项目里这种逻辑复用越多,代码越干净。
加粗一下:目录规划没有绝对标准,只要团队里所有人都能快速找到对应代码,就算合理。
最后再分享一个我自己的使用习惯:开新项目时,先花 10 分钟把路由、请求封装、状态管理和目录结构搭好,再写业务。这 10 分钟的收益,会在你整个开发周期里持续放大。别嫌麻烦,也别上来就照着 demo 狂写业务代码——等代码堆到几千行再改结构,那才叫真的麻烦。这套流程你跟着走完一遍,基本就能脱离教程独立开工了。