MV-06 路由与工程化
06 · 路由与工程化(Routing & Engineering)
📅 预计 80 分钟 | ⭐ = 高频考点 | 📌 中英术语见文末
✍️ 配套练习:网页练习shengxia.dev/quiz?module=mv06
1.0 先给直觉:路由是前台,构建工具是装修队
把网站想象成一家酒店:
- 路由(router)是前台:客人报出房号(URL),前台把他带到对应房间(页面组件)。前台还负责"客人在哪个房间",让你知道当前"住在哪一页"。
- 构建工具(build tool)是装修队:装修队把一堆原材料(一个个
.vue文件、CSS、图片)加工组合成一栋能住的房子(可部署的网站文件),顺便帮你去除冗余、压缩体积。
过去你可能是"手搓 HTML 文件直接打开",现在工程化之后,代码要加工打包再上线,这个流程要理解清楚。
💡 记忆口诀:路由管"页面在哪",工程化管"代码怎么变成能上线的网站"。
1.1 前端路由:SPA 的页面切换 ⭐
从多页面到单页面
传统网站是多页面(MPA):每个 URL 对应一个真正的 HTML 文件,点链接就向服务器请求新页面,整页刷新。
现代前端多用单页面应用(SPA):整个网站只有一个 HTML 页面,切换"页面"时不发新请求,只用 JS 替换当前显示的内容。路由的任务就是:根据 URL 决定显示哪个组件,并让浏览器的前进后退按钮正常。
hash 模式与 history 模式
前端路由有两大类实现,核心区别在于 URL 长什么样、以及是否向服务器发请求:
hash 模式: https://shengxia.dev/#/about
history 模式: https://shengxia.dev/about
- hash 模式:
#后面的部分叫 hash,它变化时不会触发浏览器向服务器请求,JS 监听hashchange事件切换内容。实现简单,任何静态服务器都能用。 - history 模式:用 HTML5 的
History API(pushState/popstate)修改地址栏 URL,页面更干净。但刷新时浏览器会真的请求/about,需要服务器把所有路径都重定向回首页(否则刷新 404)。
SPA 的好处与代价
- 好处:切换流畅无刷新、体验接近原生应用、只加载必要数据。
- 代价:首屏要下载整套 JS、SEO 相对弱、history 模式要服务器配合。
⚠️ 常见错误
- history 模式部署后刷新 404:没配置服务器回退到
index.html,一刷新就找不到页面。 - 以为 hash 会请求服务器:hash 部分根本不会发给服务器,它是纯前端的"锚点"。
- 路由跳转用
<a href>:整页刷新丢失 SPA 优势,应该用框架提供的路由链接组件(<router-link>)。
1.2 Vue Router:在 Vue 里用路由 ⭐
基本使用三步
- 定义路由表:URL 路径 → 组件的映射。
<router-view>出口:当前路由对应的组件渲染到这里。<router-link>跳转:声明式的链接,等价于"带路由的 a 标签"。
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
const routes = [
{ path: '/', name: 'home', component: Home },
{ path: '/about', name: 'about', component: About },
];
export default createRouter({
history: createWebHistory(), // history 模式;用 createWebHashHistory() 则是 hash 模式
routes,
});
<!-- App.vue -->
<template>
<nav>
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
</nav>
<router-view /> <!-- 当前路由的组件显示在这里 -->
</template>
动态路由与参数
URL 里带可变参数(比如用户 ID),用 :参数名 定义,组件里用 route.params 读取:
const routes = [
{ path: '/user/:id', name: 'user', component: User },
];
<template>
<p>当前用户 ID:{{ $route.params.id }}</p>
</template>
跳转时可以携带参数:
<router-link :to="`/user/${user.id}`">查看用户</router-link>
<!-- 或命令式跳转 -->
<button @click="$router.push(`/user/${user.id}`)">跳转</button>
嵌套路由
页面里还有子页面(如 /user/:id/profile),用 children 嵌套:
const routes = [
{
path: '/user/:id',
component: User,
children: [
{ path: 'profile', component: UserProfile }, // /user/1/profile
{ path: 'settings', component: UserSettings }, // /user/1/settings
],
},
];
User 组件里再放一个 <router-view> 承载子路由。
⚠️ 常见错误
- 把
router-link写成普通<a>:会整页刷新,SPA 的优势全丢。 - 动态路由路径写错:
/user/:id里:id必须对应组件里读取的route.params.id。 - 嵌套路由忘加子出口:子路由配了,父组件里却没放
<router-view>,子页面永远显示不出来。 - 在模板外拿路由对象:组件内要用
$route(当前路由)/$router(路由实例),别拼错。
1.3 构建工具:为什么需要 Vite / Webpack
模块化带来的"编译"需求
现代前端代码以模块为单位组织:一个文件一个组件,相互 import。但浏览器原生执行这些 .vue、.scss、ESM 语法并不完整(尤其 .vue 是 Vue 自定义格式)。构建工具负责:
- 编译转译:把
.vue、.scss、新语法转成浏览器能跑的 JS/CSS。 - 模块打包:把几十上百个文件合并、优化成少量文件(甚至一个 bundle)。
- 开发体验:本地起一个开发服务器,改代码热更新(HMR),不用手动刷新。
- 生产优化:压缩代码、去重、加文件名指纹、兼容旧浏览器。
Vite vs Webpack
| Vite | Webpack | |
|---|---|---|
| 开发启动 | 秒级(按需编译,原生 ESM) | 较慢(先全量打包) |
| 配置 | 简单,约定优先 | 灵活但配置较重 |
| 生态 | 更新,Vue 官方脚手架默认用 Vite | 老牌成熟,插件生态庞大 |
一句话:Vite 快、简单,是当下新项目的默认选择;Webpack 老牌灵活,存量项目里大量存在。学习时先掌握 Vite,理解构建流程即可。
⚠️ 常见错误
- 以为构建工具是"必须手写配置":用脚手架(如
npm create vue)自动生成即可,不必从零配 Vite。 - 混淆"开发服务器"与"线上部署":
npm run dev是本地的开发服务器,上线要用npm run build的产物。 - 直接双击打开
index.html:模块化代码需要经构建服务器提供,直接 file:// 打开会因跨域/模块加载失败。
1.4 Vite 项目结构:一个 Vue 项目的"骨架"
用脚手架创建一个 Vue 项目后,目录大致长这样:
my-app/
├── index.html # 唯一的 HTML 入口
├── package.json # 依赖与脚本(dev/build 命令都在这里)
├── vite.config.js # Vite 配置(代理、插件等)
├── public/ # 静态资源,原样复制到产物
└── src/
├── main.js # 应用入口:创建应用并挂载
├── App.vue # 根组件
├── components/ # 通用组件
├── views/ # 页面组件(路由对应的"房间")
├── router/ # 路由配置
└── store/ # 状态管理
入口文件 main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
const app = createApp(App); // 用根组件创建应用
app.use(router); // 注册路由插件
app.mount('#app'); // 挂载到 index.html 里的 <div id="app">
index.html 里有一个 <div id="app">,Vue 应用"长"在这个 div 里。
package.json 里的关键脚本
{
"scripts": {
"dev": "vite", // 启动开发服务器(热更新)
"build": "vite build", // 打包生产产物到 dist/
"preview": "vite preview" // 本地预览打包结果
}
}
开发流程:npm install 装依赖 → npm run dev 开发 → npm run build 打包 → 部署 dist/。
⚠️ 常见错误
npm run build后打开dist下的 HTML 直接看:部分路径是绝对路径,直接打开可能资源 404,用npm run preview或部署服务器预览。- 忘
npm install:克隆项目后直接npm run dev,报"找不到 vite",先装依赖。 - 改了代码但浏览器不生效:热更新偶尔失效,手动刷新或重启 dev 服务器。
1.5 单文件组件与工程化协作
SFC 是工程化的基石
.vue 单文件组件(<template> + <script> + <style scoped>)是现代 Vue 开发的"细胞"。它把结构、逻辑、样式聚在一处,构建工具负责把它编译成可运行的 JS。这正是"开发时的便利写法"与"浏览器能跑的代码"之间的桥梁。
<template>
<h1>{{ title }}</h1>
</template>
<script>
export default {
data() {
return { title: 'Hello, Vite + Vue' };
},
};
</script>
<style scoped>
h1 { color: #2b8a3e; }
</style>
开发与部署的"两套世界"
- 开发时:
.vue、.scss、模块化源码,靠 Vite 开发服务器实时编译、热更新。 - 部署时:
vite build产出dist/,里面是压缩好的 HTML/CSS/JS,浏览器直接运行,不需要 Vue 源码。
理解这两套世界,就知道为什么"代码在本地好好的,部署却出问题"——多半是构建配置、路径、或服务器配合的问题。
⚠️ 常见错误
- 把源码当产物部署:把
src/上传服务器,浏览器根本不认识.vue,必须部署构建产物。 - 相对路径资源部署后丢失:图片路径写错或用了绝对路径,打包后资源找不到。
- 环境差异:本地用开发服务器、线上是静态托管,遇到 CORS 或路径问题先想想是哪个环节。
1.6 打包与部署
build 会做什么
npm run build 会执行 Vite 的打包:编译所有模块 → 压缩代码 → 加哈希文件名 → 输出到 dist/ 目录。你看到类似这样的产物:
dist/
├── index.html
└── assets/
├── index-abc123.css
└── index-def456.js
文件名带哈希(abc123)是为了缓存控制:代码变了哈希就变,浏览器就会加载新文件,不会用旧缓存。
部署到静态托管
dist/ 是纯静态文件,可以部署到任意静态托管服务(如 Vercel、GitHub Pages、Nginx)。要点:
- history 路由回退:如果用了 history 模式,服务器要配置"所有未知路径 → 返回
index.html",否则刷新 404。 - 环境变量:不同环境(开发/生产)的接口地址等用环境变量区分,
.env.development/.env.production。 - CI/CD:可以配置"代码推送后自动构建部署",省去手动上传。
一个部署检查清单
npm run build成功,无报错。npm run preview本地预览dist/正常。- history 路由的刷新回退已配置。
- 静态资源路径正确、接口地址符合线上环境。
⚠️ 常见错误
- 构建后路径不对:Vite 默认相对路径,子路径部署要设
base;根路径部署则用默认即可。 - history 模式没配回退:线上刷新子路由 404,是最常见的部署翻车点。
- 忘记
.gitignore掉node_modules和dist:大目录进仓库,臃肿且易冲突,node_modules由npm install重建。
1.7 路由守卫与按需加载
路由守卫:进门前先检查
有些页面需要"登录才能进"(如个人中心),有些页面进入时要拉取数据。路由守卫(guard)在路由切换前后执行钩子,最常见的用途是权限控制:
// router/index.js
router.beforeEach((to, from, next) => {
const isLoggedIn = localStorage.getItem('token'); // 是否登录
if (to.meta.requiresAuth && !isLoggedIn) {
next('/login'); // 未登录:拦下来,重定向到登录页
} else {
next(); // 放行
}
});
to 是目标路由,from 是当前路由,next() 决定是否继续跳转。配合路由元信息 meta 标记"哪些页面需要权限",一套登录拦截就写好了。
懒加载:路由到用时才加载
SPA 把所有代码打包成一个文件,首屏要下载的内容很多。路由懒加载(lazy loading)把每个页面的代码拆成独立文件,访问到该路由时才加载:
const routes = [
// 原先:直接引入组件
// { path: '/about', component: About },
// 懒加载:访问 /about 时才请求 About.vue 对应的代码块
{ path: '/about', component: () => import('../views/About.vue') },
];
写法就是把 component 改成 () => import(...) 的函数形式。效果:首屏只加载首页,其他页面"要用才下",首屏加载明显变快。
工程化视角:性能与体验
路由 + 工程化的组合拳,常见优化手段还有:
- 代码分割:不仅路由按需加载,大的第三方库也可单独拆包。
- 预加载:用户悬停链接时提前下载目标页资源(
prefetch)。 - 构建分析:用构建工具的报告看哪些模块体积大,针对性优化。
理解这些,你就明白了"单页面应用"并非把所有代码一次塞给用户——按需、分步才是工程化的常态。
⚠️ 常见错误
- 守卫里忘记调用
next():既不放行也不跳转,页面卡住不动,守卫必须有一个出口。 - 懒加载写成立即执行:
component: import(...)少了() =>,会在定义时立刻加载,失去懒加载意义。 - 权限只在前端拦:前端守卫只是体验层,真正的数据安全必须靠后端接口鉴权,别只依赖前端。
- 过度预加载:所有路由都 prefetch,等于回到"全部加载",要克制。
1.8 环境变量与多环境部署
为什么需要环境变量
同一个项目,开发时请求本地接口,上线后请求线上接口。把这些"因环境而不同"的配置抽成环境变量,代码里不再写死地址,换环境只改配置:
# .env.development(开发环境)
VITE_API_BASE = /api
# .env.production(生产环境)
VITE_API_BASE = https://api.shengxia.dev
代码里通过 import.meta.env.VITE_API_BASE 读取(Vite 约定以 VITE_ 开头的变量才会暴露给前端代码)。这样"开发连本地、上线连线上"由构建时自动切换,代码无需改动。
环境变量怎么用
// 请求接口的公共地址,随环境自动变化
const baseURL = import.meta.env.VITE_API_BASE;
fetch(`${baseURL}/login`)
.then((res) => res.json())
.then((data) => console.log(data));
注意:VITE_ 开头的变量会打进前端代码包,浏览器里可见,绝不能放密钥(API Key、密码)。真正的密钥要放服务器端,前端放"可公开的配置"(接口地址、开关)。
部署的完整链路
一套标准的工程化上线流程大致是:
- 本地
npm run build验证能打包。 - 通过 CI(持续集成)在代码推送后自动构建,产物上传到托管平台。
- 配置域名与 HTTPS,history 路由配好回退。
- 上线后用浏览器开发者工具确认请求、资源、路由都正常。
前端工程化不是"能写页面"就够了,从代码到上线的整条链路都值得掌握。
⚠️ 常见错误
- 把密钥写进
VITE_变量:前端包任何人都能看,等于公开密钥,必须放服务器端。 - 代码里写死接口地址:环境一变就要改代码重新发版,用环境变量分离。
- 忘了
import.meta.env是构建时注入:它是打包时替换的静态值,不是运行时读取的文件,理解这点才不会误解它的机制。
📌 记忆口诀
- 路由:URL 是房号,路由是前台,SPA 换页不刷新。
- hash vs history:hash 有
#不发请求,history 干净但要服务器回退。 - 构建工具:把源码加工成浏览器能跑的产物;开发用 dev,上线用 build。
- 部署三连:
npm install→npm run build→ 部署dist/,history 记得配回退。
📌 双语术语表(本讲)
| 中文 | English | 记忆点 |
|---|---|---|
| 路由 | router | URL → 组件的映射 |
| 单页面应用 | SPA | 一个 HTML 页面 |
| 多页面应用 | MPA | 一页一个 HTML |
| 路由出口 | router-view | 组件渲染位置 |
| 路由链接 | router-link | 带路由的 a 标签 |
| 动态路由 | dynamic route | :id 参数 |
| 构建工具 | build tool | Vite / Webpack |
| 热更新 | HMR | 改代码即生效 |
| 打包 | bundle | 合并压缩源码 |
| 部署 | deploy | 产物上线 |
⭐ 本讲考点清单
- SPA 与 MPA 的区别、SPA 的优缺点
- hash 模式与 history 模式的区别与适用场景
- Vue Router 三步:路由表、
router-view、router-link - 动态路由
:id与route.params的读取 - 嵌套路由与子出口
router-view - 构建工具的作用:编译、打包、热更新、优化
- Vite 与 Webpack 的差异与选型
- 项目结构:
main.js、App.vue、package.json脚本 npm run dev/build/preview的区别- 部署流程:
dist/产物、history 回退、静态托管