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 APIpushState / popstate)修改地址栏 URL,页面更干净。但刷新时浏览器会真的请求 /about,需要服务器把所有路径都重定向回首页(否则刷新 404)。

SPA 的好处与代价

  • 好处:切换流畅无刷新、体验接近原生应用、只加载必要数据。
  • 代价:首屏要下载整套 JS、SEO 相对弱、history 模式要服务器配合。

⚠️ 常见错误

  1. history 模式部署后刷新 404:没配置服务器回退到 index.html,一刷新就找不到页面。
  2. 以为 hash 会请求服务器:hash 部分根本不会发给服务器,它是纯前端的"锚点"。
  3. 路由跳转用 <a href>:整页刷新丢失 SPA 优势,应该用框架提供的路由链接组件(<router-link>)。

1.2 Vue Router:在 Vue 里用路由 ⭐

基本使用三步

  1. 定义路由表:URL 路径 → 组件的映射。
  2. <router-view> 出口:当前路由对应的组件渲染到这里。
  3. <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> 承载子路由。

⚠️ 常见错误

  1. router-link 写成普通 <a>:会整页刷新,SPA 的优势全丢。
  2. 动态路由路径写错/user/:id:id 必须对应组件里读取的 route.params.id
  3. 嵌套路由忘加子出口:子路由配了,父组件里却没放 <router-view>,子页面永远显示不出来。
  4. 在模板外拿路由对象:组件内要用 $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,理解构建流程即可。

⚠️ 常见错误

  1. 以为构建工具是"必须手写配置":用脚手架(如 npm create vue)自动生成即可,不必从零配 Vite。
  2. 混淆"开发服务器"与"线上部署"npm run dev 是本地的开发服务器,上线要用 npm run build 的产物。
  3. 直接双击打开 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/

⚠️ 常见错误

  1. npm run build 后打开 dist 下的 HTML 直接看:部分路径是绝对路径,直接打开可能资源 404,用 npm run preview 或部署服务器预览。
  2. npm install:克隆项目后直接 npm run dev,报"找不到 vite",先装依赖。
  3. 改了代码但浏览器不生效:热更新偶尔失效,手动刷新或重启 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 源码

理解这两套世界,就知道为什么"代码在本地好好的,部署却出问题"——多半是构建配置、路径、或服务器配合的问题。

⚠️ 常见错误

  1. 把源码当产物部署:把 src/ 上传服务器,浏览器根本不认识 .vue,必须部署构建产物。
  2. 相对路径资源部署后丢失:图片路径写错或用了绝对路径,打包后资源找不到。
  3. 环境差异:本地用开发服务器、线上是静态托管,遇到 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:可以配置"代码推送后自动构建部署",省去手动上传。

一个部署检查清单

  1. npm run build 成功,无报错。
  2. npm run preview 本地预览 dist/ 正常。
  3. history 路由的刷新回退已配置。
  4. 静态资源路径正确、接口地址符合线上环境。

⚠️ 常见错误

  1. 构建后路径不对:Vite 默认相对路径,子路径部署要设 base;根路径部署则用默认即可。
  2. history 模式没配回退:线上刷新子路由 404,是最常见的部署翻车点。
  3. 忘记 .gitignorenode_modulesdist:大目录进仓库,臃肿且易冲突,node_modulesnpm 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)。
  • 构建分析:用构建工具的报告看哪些模块体积大,针对性优化。

理解这些,你就明白了"单页面应用"并非把所有代码一次塞给用户——按需、分步才是工程化的常态。

⚠️ 常见错误

  1. 守卫里忘记调用 next():既不放行也不跳转,页面卡住不动,守卫必须有一个出口。
  2. 懒加载写成立即执行component: import(...) 少了 () =>,会在定义时立刻加载,失去懒加载意义。
  3. 权限只在前端拦:前端守卫只是体验层,真正的数据安全必须靠后端接口鉴权,别只依赖前端。
  4. 过度预加载:所有路由都 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、密码)。真正的密钥要放服务器端,前端放"可公开的配置"(接口地址、开关)。

部署的完整链路

一套标准的工程化上线流程大致是:

  1. 本地 npm run build 验证能打包。
  2. 通过 CI(持续集成)在代码推送后自动构建,产物上传到托管平台。
  3. 配置域名与 HTTPS,history 路由配好回退。
  4. 上线后用浏览器开发者工具确认请求、资源、路由都正常。

前端工程化不是"能写页面"就够了,从代码到上线的整条链路都值得掌握。

⚠️ 常见错误

  1. 把密钥写进 VITE_ 变量:前端包任何人都能看,等于公开密钥,必须放服务器端。
  2. 代码里写死接口地址:环境一变就要改代码重新发版,用环境变量分离。
  3. 忘了 import.meta.env 是构建时注入:它是打包时替换的静态值,不是运行时读取的文件,理解这点才不会误解它的机制。

📌 记忆口诀

  • 路由:URL 是房号,路由是前台,SPA 换页不刷新。
  • hash vs history:hash 有 # 不发请求,history 干净但要服务器回退。
  • 构建工具:把源码加工成浏览器能跑的产物;开发用 dev,上线用 build。
  • 部署三连npm installnpm 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 产物上线

⭐ 本讲考点清单

  1. SPA 与 MPA 的区别、SPA 的优缺点
  2. hash 模式与 history 模式的区别与适用场景
  3. Vue Router 三步:路由表、router-viewrouter-link
  4. 动态路由 :idroute.params 的读取
  5. 嵌套路由与子出口 router-view
  6. 构建工具的作用:编译、打包、热更新、优化
  7. Vite 与 Webpack 的差异与选型
  8. 项目结构:main.jsApp.vuepackage.json 脚本
  9. npm run dev / build / preview 的区别
  10. 部署流程:dist/ 产物、history 回退、静态托管