前端 Vue 专栏 09:Vue Router 与前端路由
前言
在传统多页面网站中,从用户列表进入用户详情,浏览器往往会重新向服务器请求一份 HTML:
text
/users
↓ 点击用户
/users/1001
↓ 浏览器重新请求 HTML
服务器返回新的页面
但在 Vue 单页应用中,我们点击 <RouterLink> 后通常会发现:
text
地址栏发生变化;
浏览器没有刷新整张页面;
页面中只有 RouterView 区域切换了组件;
组件仍然可以读取 URL 中的参数。
这背后就是前端路由。
这一篇将围绕秋招和实际开发最常见的问题展开:
text
什么是 SPA 和前端路由;
前端路由为什么不刷新页面;
RouterLink 和 RouterView 分别做什么;
route 和 router 有什么区别;
params 和 query 怎么选择;
hash 与 history 模式有什么区别;
history 模式刷新为什么可能 404;
导航守卫怎样实现登录校验;
路由懒加载为什么能减小首屏体积。
一、什么是路由
路由最基本的作用是:
根据当前 URL,决定应该展示什么内容。
例如:
text
/ → 首页组件
/users → 用户列表组件
/users/1001 → 用户详情组件
/settings/profile → 个人设置组件
可以把路由表理解成 URL 与组件的映射表:
js
const routes = [
{
path: '/',
component: HomeView
},
{
path: '/users',
component: UserListView
},
{
path: '/users/:id',
component: UserDetailView
}
]
当地址是:
text
/users/1001
Vue Router 会匹配:
text
/users/:id
然后把 UserDetailView 渲染到 <RouterView> 中,并得到:
js
route.params.id === '1001'
二、什么是 SPA
SPA 是 Single Page Application,中文叫单页应用。
"单页"不是指应用只有一个业务页面,而是指浏览器通常只在第一次进入应用时获取主要 HTML 文档:
text
第一次访问
↓
服务器返回 index.html
↓
加载 Vue、路由和业务 JavaScript
↓
Vue 接管页面
后续从首页进入用户列表时,通常不会重新请求一份完整 HTML,而是由前端路由切换当前展示的组件:
text
HomeView
↓
UserListView
当然,"不刷新整页"不代表完全没有网络请求。路由切换时仍然可能请求:
text
接口数据;
懒加载的 JavaScript 文件;
图片、字体等资源。
只是浏览器没有把当前文档全部销毁后再加载一份新 HTML。
三、前端路由为什么可以不刷新页面
前端路由主要完成四件事:
text
监听或修改浏览器 URL;
根据 URL 匹配路由记录;
更新当前 route 对象;
让 RouterView 渲染匹配到的组件。
以 history 模式为例,简化过程是:
text
用户点击 RouterLink
↓
Vue Router 阻止链接的默认整页跳转
↓
调用 History API 修改地址栏
↓
根据新地址匹配路由表
↓
更新响应式的当前路由
↓
RouterView 切换组件
浏览器提供了:
js
history.pushState()
history.replaceState()
它们可以修改地址栏和历史记录,但不会像普通页面导航一样自动重新请求整份 HTML。
浏览器前进、后退时,Vue Router 还会监听相应历史变化,再次匹配并切换组件。
因此不是 Vue Router 拦截了所有网络请求,而是:
它利用浏览器提供的 URL 与 History 能力,在不重新加载文档的情况下维护地址,并让 URL 驱动组件切换。
四、RouterLink 和普通 a 标签有什么区别
普通链接:
html
<a href="/users">用户列表</a>
浏览器默认会把它当作页面导航,向 /users 发起文档请求。
Vue Router 提供:
vue
<RouterLink to="/users">
用户列表
</RouterLink>
它最终通常仍然渲染为 <a>,因此保留链接语义;但普通左键导航时,Vue Router 会接管跳转,避免整页刷新。
它还会处理:
text
URL 生成和编码;
history 记录;
当前路由激活样式;
对象形式的命名路由;
浏览器前进和后退的配合。
例如:
vue
<RouterLink
:to="{
name: 'user-detail',
params: { id: user.id }
}"
>
查看详情
</RouterLink>
如果是站外地址、下载链接或确实需要整页导航,仍然可以使用普通 <a>。
五、RouterView 是什么
<RouterView> 是路由组件的出口:
当前 URL 匹配到了哪个路由组件,就把它渲染到 RouterView 所在的位置。
例如 App.vue:
vue
<template>
<header>
<RouterLink to="/">首页</RouterLink>
<RouterLink to="/users">用户管理</RouterLink>
</header>
<main>
<RouterView />
</main>
</template>
访问 / 时:
text
RouterView → HomeView
访问 /users 时:
text
RouterView → UserListView
页面中的 header 不需要重新创建,主要变化的是 RouterView 对应区域。
如果只配置路由却没有写 <RouterView>,Vue Router 虽然可以匹配地址,但没有位置展示匹配到的组件。
六、在 Vue 3 项目中配置 Vue Router
常见目录结构:
text
src/
├── router/
│ └── index.js
├── views/
│ ├── HomeView.vue
│ ├── UserListView.vue
│ └── UserDetailView.vue
├── App.vue
└── main.js
1. 创建路由实例
js
// src/router/index.js
import {
createRouter,
createWebHistory
} from 'vue-router'
import HomeView from '@/views/HomeView.vue'
import UserListView from '@/views/UserListView.vue'
import UserDetailView from '@/views/UserDetailView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/users',
name: 'user-list',
component: UserListView
},
{
path: '/users/:id',
name: 'user-detail',
component: UserDetailView
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
2. 安装路由插件
js
// src/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')
app.use(router) 会把路由能力安装到整个 Vue 应用,使组件中可以使用:
text
RouterLink;
RouterView;
useRouter;
useRoute;
路由相关的全局属性。
安装顺序通常是:
js
app.use(router)
app.mount('#app')
因为应用挂载前就应该准备好路由能力。
七、路由配置中常见字段
js
const routes = [
{
path: '/users/:id',
name: 'user-detail',
component: UserDetailView,
props: true,
meta: {
requiresAuth: true,
title: '用户详情'
}
}
]
常见字段:
| 字段 | 作用 |
|---|---|
path |
要匹配的 URL 路径 |
name |
路由名称,方便稳定跳转 |
component |
匹配后渲染的组件 |
children |
嵌套路由 |
redirect |
重定向到另一个地址 |
alias |
为同一路由提供其他访问地址 |
props |
把路由参数作为组件 props 传递 |
meta |
保存权限、标题等附加信息 |
beforeEnter |
当前路由独享守卫 |
秋招阶段优先掌握:
text
path、name、component、children、meta、redirect
其余字段知道使用场景即可。
八、route 和 router 有什么区别
这是 Vue Router 最常见的问题之一。
vue
<script setup>
import {
useRoute,
useRouter
} from 'vue-router'
const route = useRoute()
const router = useRouter()
</script>
route
route 表示当前路由信息,是"当前地址的结果":
js
console.log(route.path)
console.log(route.fullPath)
console.log(route.name)
console.log(route.params)
console.log(route.query)
console.log(route.meta)
例如当前地址是:
text
/users/1001?tab=posts
可能得到:
js
route.path
// '/users/1001'
route.params
// { id: '1001' }
route.query
// { tab: 'posts' }
router
router 是路由器实例,是"控制导航的工具":
js
router.push('/users')
router.replace('/login')
router.back()
router.forward()
router.go(-1)
最简洁的记忆方式:
text
route:当前在哪里
router:控制去哪里
在选项式 API 中对应:
text
this.$route → 当前路由信息
this.$router → 路由器实例
九、编程式导航:push、replace 和 go
除了 <RouterLink>,还可以在 JavaScript 中跳转:
vue
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
function openUser(userId) {
router.push({
name: 'user-detail',
params: { id: userId }
})
}
</script>
1. push
js
router.push('/users')
向浏览器历史栈增加一条记录:
text
/home
↓ push('/users')
/users
用户点击浏览器后退,可以回到 /home。
2. replace
js
router.replace('/home')
替换当前历史记录,不额外增加一条:
text
/login
↓ replace('/home')
/home
登录成功后经常使用 replace,避免用户后退又回到登录页。
3. go、back 和 forward
js
router.go(-1) // 后退一条
router.go(1) // 前进一条
router.back() // 后退
router.forward() // 前进
对比:
| 方法 | 是否新增历史记录 | 常见用途 |
|---|---|---|
push |
是 | 普通页面跳转 |
replace |
否,替换当前记录 | 登录完成、纠正无效地址 |
go |
不创建,移动历史指针 | 前进或后退 |
十、动态路由参数 params
假设所有用户详情都使用同一个组件:
text
/users/1001
/users/1002
/users/1003
不需要为每个用户写一条路由,可以使用动态参数:
js
{
path: '/users/:id',
name: 'user-detail',
component: UserDetailView
}
其中:
text
:id 是动态路径参数
在组件中读取:
vue
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id)
</script>
<template>
<h1>用户 {{ route.params.id }}</h1>
</template>
访问:
text
/users/1001
得到:
js
route.params.id === '1001'
注意 URL 中的 params 通常是字符串,而不是自动转换成数字:
js
Number(route.params.id)
如果业务需要数字,应当主动转换和校验。
十一、为什么从 users/1 到 users/2 没有重新 mounted
路由配置:
js
{
path: '/users/:id',
component: UserDetailView
}
从:
text
/users/1
跳到:
text
/users/2
前后匹配的都是 UserDetailView。Vue Router 通常会复用同一个组件实例,而不是先卸载用户 1 组件,再创建用户 2 组件。
因此:
text
setup 不会重新执行;
onMounted 不会重新执行;
组件中的局部状态可能继续保留。
如果最初只在 onMounted 中请求数据:
js
onMounted(() => {
loadUser(route.params.id)
})
从 /users/1 跳到 /users/2 时,可能不会重新请求。
更合理的是监听具体参数:
vue
<script setup>
import { watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
watch(
() => route.params.id,
id => {
loadUser(id)
},
{ immediate: true }
)
</script>
也可以使用组件内守卫:
js
import { onBeforeRouteUpdate } from 'vue-router'
onBeforeRouteUpdate(to => {
return loadUser(to.params.id)
})
面试时应该说明原因:
路由参数变化不一定意味着组件类型变化。相同路由组件会被复用,所以不能只依赖 mounted,需要 watch 具体参数或使用 onBeforeRouteUpdate。
十二、查询参数 query
查询参数位于 ? 后面:
text
/users?page=2&keyword=vue
读取方式:
js
const route = useRoute()
console.log(route.query.page)
// '2'
console.log(route.query.keyword)
// 'vue'
跳转时可以写:
js
router.push({
path: '/users',
query: {
page: 2,
keyword: 'vue'
}
})
生成类似:
text
/users?page=2&keyword=vue
query 也通常以字符串形式读取,因此分页参数常需要转换:
js
const page = Number(route.query.page ?? 1)
常见用途:
text
分页页码;
搜索关键词;
筛选条件;
当前标签;
排序方式。
把筛选条件放在 URL 中,可以让刷新、复制链接和浏览器前进后退更符合用户预期。
十三、params 和 query 有什么区别
例如用户详情:
text
/users/1001
这里的 1001 是路径的一部分,适合使用 params:
js
path: '/users/:id'
例如用户列表筛选:
text
/users?page=2&status=active
分页和状态是附加条件,适合使用 query。
对比:
| 对比项 | params | query |
|---|---|---|
| URL 位置 | 路径内部 | ? 后面 |
| 示例 | /users/1001 |
/users?page=2 |
| 路由是否需要声明 | 通常要在 path 中声明 :id |
不需要预先声明字段 |
| 常见用途 | 资源身份、页面层级 | 搜索、分页、筛选、排序 |
| 读取方式 | route.params.id |
route.query.page |
判断原则:
text
缺少它就无法确定访问哪个资源 → params
它只是当前页面的附加筛选状态 → query
但这不是强制语法规则,最终还要结合接口设计和产品 URL 规范。
十四、为什么推荐命名路由
可以直接使用路径跳转:
js
router.push(`/users/${userId}`)
也可以使用命名路由:
js
router.push({
name: 'user-detail',
params: {
id: userId
}
})
命名路由的优点是:
text
不需要手动拼接 URL;
参数会由路由器进行编码;
path 结构变化时,调用方仍可通过稳定 name 跳转;
代码语义更清晰。
一个常见错误是同时传入 path 和 params:
js
router.push({
path: '/users',
params: { id: 1001 }
})
不要期待它自动生成:
text
/users/1001
需要动态 params 时,优先使用:
js
router.push({
name: 'user-detail',
params: { id: 1001 }
})
或者自己提供完整 path:
js
router.push({
path: '/users/1001'
})
十五、为什么可以把 params 作为 props
直接在组件中使用 route.params:
js
const route = useRoute()
const userId = route.params.id
会让组件与路由系统耦合。
可以在路由配置中写:
js
{
path: '/users/:id',
name: 'user-detail',
component: UserDetailView,
props: true
}
然后组件像接收普通 prop 一样接收 id:
vue
<script setup>
const props = defineProps({
id: {
type: String,
required: true
}
})
</script>
<template>
<h1>用户 {{ id }}</h1>
</template>
这样组件更容易:
text
脱离路由单独复用;
编写组件测试;
明确声明输入数据。
这是工程上的加分项,但不是每个简单页面都必须使用。
十六、嵌套路由
管理后台常见结构:
text
/settings/profile
/settings/security
外层设置布局保持不变,内部区域切换不同页面。
路由配置:
js
const routes = [
{
path: '/settings',
component: SettingsLayout,
children: [
{
path: '',
redirect: { name: 'settings-profile' }
},
{
path: 'profile',
name: 'settings-profile',
component: ProfileSettings
},
{
path: 'security',
name: 'settings-security',
component: SecuritySettings
}
]
}
]
父组件 SettingsLayout.vue 中还需要一个 <RouterView>:
vue
<template>
<div class="settings-layout">
<aside>
<RouterLink to="/settings/profile">
个人资料
</RouterLink>
<RouterLink to="/settings/security">
安全设置
</RouterLink>
</aside>
<section>
<RouterView />
</section>
</div>
</template>
访问 /settings/security 时:
text
App.vue 的 RouterView
↓ 渲染 SettingsLayout
SettingsLayout 的 RouterView
↓ 渲染 SecuritySettings
需要注意,子路由的 path 通常不以 / 开头:
js
path: 'security'
它会与父路径组合为:
text
/settings/security
十七、hash 模式是什么
hash 模式使用:
js
import {
createRouter,
createWebHashHistory
} from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes
})
URL 类似:
text
https://example.com/#/users/1001
# 后面的内容属于 URL hash。浏览器向服务器请求文档时,不会把 hash 部分发送给服务器。
也就是说直接刷新:
text
https://example.com/#/users/1001
服务器主要收到的是:
text
GET /
所以服务器只要能够返回根路径的 index.html,前端再读取 hash 并展示用户详情即可。
hash 模式的特点:
text
部署简单;
通常不需要额外 history 回退配置;
URL 中带有 #;
URL 形式不如 history 模式自然;
对 SEO 场景通常不够理想。
十八、history 模式是什么
history 模式使用:
js
import {
createRouter,
createWebHistory
} from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes
})
URL 类似普通地址:
text
https://example.com/users/1001
应用内部跳转时,Vue Router 利用 History API 修改 URL,不重新加载整张页面。
特点:
text
URL 干净自然;
更接近传统网站路径;
通常是现代 Web 应用常用模式;
服务器必须配置 SPA 回退。
对比:
| 对比项 | hash | history |
|---|---|---|
| URL | /#/users/1 |
/users/1 |
| 核心能力 | hash 变化 | History API |
| hash 后内容是否发给服务器 | 否 | 不适用 |
| 刷新深层地址 | 通常直接返回根页面 | 需要服务器 fallback |
| URL 形式 | 带 # |
更自然 |
秋招回答不能只说"hash 丑、history 好看",还要说出服务器请求的区别。
十九、history 模式刷新为什么会 404
假设用户先访问:
text
https://example.com/
服务器返回 index.html,Vue 应用启动。然后前端跳转到:
text
https://example.com/users/1001
此时只是前端路由切换,所以没有问题。
但是用户按下刷新,浏览器会真正向服务器发送:
http
GET /users/1001 HTTP/1.1
Host: example.com
服务器文件系统中通常没有:
text
/users/1001
它可能直接返回 404,Vue 应用甚至没有机会启动。
解决方式是让服务器执行 SPA 回退:
text
如果请求的是现有静态资源 → 返回对应文件
如果请求路径找不到文件 → 返回 index.html
Nginx 常见配置:
nginx
location / {
try_files $uri $uri/ /index.html;
}
流程变成:
text
浏览器请求 /users/1001
↓
Nginx 找不到对应静态文件
↓
回退返回 index.html
↓
Vue 应用启动
↓
Vue Router 读取 /users/1001
↓
渲染 UserDetailView
如果同一 Nginx 还代理接口,通常需要把 API 规则单独配置:
nginx
location /api/ {
proxy_pass http://backend-server/;
}
location / {
try_files $uri $uri/ /index.html;
}
这样:
text
/api/users/1001 → 转发给后端
/users/1001 → 返回前端 index.html
服务器 fallback 解决的是"让 Vue 应用有机会启动",前端仍应配置自己的 404 路由,负责显示用户能看到的未找到页面。
二十、什么是导航守卫
导航守卫是在路由切换过程中执行的函数,用来决定:
text
是否允许进入;
是否取消导航;
是否重定向;
是否等待异步校验完成。
最常见的是全局前置守卫:
js
router.beforeEach((to, from) => {
console.log('准备从', from.fullPath)
console.log('进入', to.fullPath)
})
其中:
text
to:准备进入的目标路由
from:即将离开的当前路由
常见返回值:
js
return true
// 或不返回:继续导航
return false
// 取消导航
return { name: 'login' }
// 重定向到登录页
守卫也可以是异步函数:
js
router.beforeEach(async to => {
const allowed = await checkPermission(to)
if (!allowed) {
return { name: 'forbidden' }
}
})
现代写法优先通过 return 控制导航。旧代码中常见的第三个参数 next 仍被支持,但分支复杂时容易重复调用或漏调用,不建议新代码机械沿用。
二十一、使用 meta 实现登录校验
在路由配置中标记需要登录的页面:
js
const routes = [
{
path: '/login',
name: 'login',
component: LoginView
},
{
path: '/dashboard',
name: 'dashboard',
component: DashboardView,
meta: {
requiresAuth: true
}
}
]
全局守卫统一判断:
js
router.beforeEach(to => {
const token = localStorage.getItem('token')
if (to.meta.requiresAuth && !token) {
return {
name: 'login',
query: {
redirect: to.fullPath
}
}
}
})
过程是:
text
访问 /dashboard
↓
beforeEach 检查 requiresAuth
↓
发现没有 token
↓
跳转 /login?redirect=/dashboard
登录成功后可以返回原页面:
js
const redirect = route.query.redirect
router.replace(
typeof redirect === 'string'
? redirect
: '/dashboard'
)
必须注意:
前端路由守卫只负责页面访问体验,不能代替后端权限校验。
用户可以修改前端代码、localStorage 或直接请求接口,所以后端仍然必须验证 token 和用户权限。
二十二、三类常见导航守卫
1. 全局守卫
js
router.beforeEach((to, from) => {})
router.beforeResolve((to, from) => {})
router.afterEach((to, from, failure) => {})
常见用途:
text
全局登录校验;
全局权限判断;
页面标题设置;
埋点和访问统计。
afterEach 在导航完成后执行,不能再取消当前导航,适合埋点等后置逻辑。
2. 路由独享守卫
js
{
path: '/admin',
component: AdminView,
beforeEnter: to => {
if (!isAdmin()) {
return { name: 'forbidden' }
}
}
}
只与某条路由相关的特殊校验,可以放在 beforeEnter。
3. 组件内守卫
组合式 API 常用:
js
import {
onBeforeRouteLeave,
onBeforeRouteUpdate
} from 'vue-router'
onBeforeRouteUpdate(to => {
loadUser(to.params.id)
})
onBeforeRouteLeave(() => {
if (formChanged.value) {
return window.confirm('内容尚未保存,确定离开吗?')
}
})
对应场景:
text
onBeforeRouteUpdate:组件被复用,但路由参数变化
onBeforeRouteLeave:离开当前页面前确认或清理
选项式 API 中还存在 beforeRouteEnter。它执行时目标组件实例还没有创建,所以不能直接访问组件 this。
二十三、路由懒加载
静态导入:
js
import UserListView from '@/views/UserListView.vue'
const routes = [
{
path: '/users',
component: UserListView
}
]
构建时,这个页面代码可能被合并到初始 JavaScript 中,即使用户没有进入用户页面,也需要在首屏下载相关代码。
路由懒加载使用动态 import:
js
const routes = [
{
path: '/users',
component: () => import('@/views/UserListView.vue')
},
{
path: '/users/:id',
component: () => import('@/views/UserDetailView.vue')
}
]
构建工具通常会把这些页面拆成独立代码块:
text
首次进入首页
→ 不必立即下载所有用户页面代码
第一次进入 /users
→ 加载 UserListView 对应代码块
它的价值是:
text
减少首屏一次性 JavaScript 体积;
把低频页面代码延后到真正访问时加载;
改善大型应用首屏加载压力。
路由懒加载不要写成:
js
component: defineAsyncComponent(
() => import('@/views/UserListView.vue')
)
Vue Router 自己支持返回 Promise 的组件加载函数,路由组件直接写:
js
component: () => import('@/views/UserListView.vue')
即可。
二十四、前端 404 路由
可以使用兜底路由匹配没有配置的地址:
js
const routes = [
// 其他路由
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/views/NotFoundView.vue')
}
]
当用户访问:
text
/this-page-does-not-exist
Vue Router 会渲染 NotFoundView。
这里需要区分两层 404:
text
服务器 404:index.html 都没有返回,Vue 没有启动
前端路由 404:Vue 已经启动,但没有匹配到业务路由
history 模式通常需要:
text
服务器 fallback 返回 index.html
+
Vue Router 兜底路由展示 NotFoundView
二者共同工作。
二十五、页面标题和滚动位置
1. 页面标题
路由配置:
js
{
path: '/users',
component: UserListView,
meta: {
title: '用户管理'
}
}
导航完成后设置标题:
js
router.afterEach(to => {
document.title = to.meta.title
? `${to.meta.title} - 管理系统`
: '管理系统'
})
2. 滚动位置
创建路由时可以配置:
js
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
}
return { top: 0 }
}
})
这样浏览器前进后退时可以恢复保存的位置,普通进入新页面时回到顶部。
这属于常见工程体验优化,面试中知道配置入口即可。
二十六、常见错误理解
1. SPA 完全不会发送页面请求
错误。
首次进入和刷新时仍然需要请求 index.html,路由切换也可能加载异步代码和接口数据。SPA 主要是不在每次内部导航时重新加载整份文档。
2. RouterLink 不是 a 标签
不准确。
RouterLink 是 Vue Router 组件,但默认通常会渲染具有链接语义的 <a>,并接管适合由前端路由处理的导航。
3. route 和 router 是同一个对象
错误。
route 保存当前路由信息,router 提供跳转和路由管理方法。
4. params 和 query 会自动转换类型
错误。
URL 参数通常按字符串读取,需要根据业务主动转换、校验和提供默认值。
5. params 变化一定重新创建组件
错误。
如果仍然匹配相同组件,组件实例通常会被复用,mounted 不会重新执行。
6. history 模式不需要服务器配置
错误。
刷新深层地址时浏览器会直接请求对应 path,服务器通常需要回退到 index.html。
7. 前端守卫能保证接口安全
错误。
前端守卫只能改善访问流程,真正的身份认证和权限控制必须由后端执行。
8. afterEach 可以取消导航
错误。
它发生在导航确认之后,适合标题、埋点等后置操作,不能再取消已经完成的导航。
9. 修改整个 route 对象最适合监听参数
不推荐。
route 是响应式对象,但通常应该监听真正关心的字段:
js
watch(
() => route.params.id,
id => loadUser(id)
)
这样依赖和业务意图更清晰。
二十七、面试高频问题
1. 什么是前端路由
可以这样回答:
前端路由根据浏览器 URL 匹配并渲染对应组件。在 SPA 内部导航时,它通过 hash 或 History API 更新地址,维护当前路由状态,再让 RouterView 切换组件,通常不需要重新加载整份 HTML 文档。
2. Vue Router 为什么切换页面不刷新
可以这样回答:
RouterLink 会接管适合前端处理的链接导航。history 模式利用 pushState、replaceState 和历史事件维护 URL,hash 模式监听 hash 变化;路由器根据新地址匹配路由记录并更新响应式 route,RouterView 随后渲染对应组件,所以不需要浏览器重新请求并加载整份 HTML。
3. RouterLink 和 RouterView 分别是什么
可以这样回答:
RouterLink 用于声明路由链接,默认通常生成 a 标签,并由 Vue Router 接管内部导航;RouterView 是路由组件出口,当前 URL 匹配到的组件会渲染在它所在的位置。嵌套路由需要在父路由组件中继续提供 RouterView。
4. route 和 router 有什么区别
可以这样回答:
route 表示当前路由信息,可以读取 path、params、query 和 meta;router 是 createRouter 创建的路由器实例,用于 push、replace、back 等导航操作。简单说,route 表示当前在哪里,router 用来控制去哪里。
5. params 和 query 有什么区别
可以这样回答:
params 通常是路径结构的一部分,例如
/users/:id中的用户 id,用于标识具体资源;query 位于问号后,例如分页、搜索和筛选条件。二者从 URL 读取时通常都是字符串,需要业务层进行类型转换和校验。
6. hash 和 history 有什么区别
可以这样回答:
hash 模式的路由位于
#后,hash 不会随文档请求发送到服务器,因此部署简单,但 URL 带井号;history 模式利用 History API,URL 更自然,但刷新/users/1时服务器会收到这个路径,所以必须配置 SPA fallback,把未命中的前端路由返回到 index.html。
7. history 模式刷新 404 怎样解决
可以这样回答:
应在静态服务器或网关上配置回退规则:现有静态资源正常返回,其他前端路径统一返回 index.html,让 Vue 应用启动后再由 Vue Router 匹配 URL。Nginx 常用
try_files $uri $uri/ /index.html,同时 API 路径要单独配置反向代理。
8. 路由守卫有哪些
可以这样回答:
守卫分为全局守卫、路由独享守卫和组件内守卫。beforeEach 常做全局登录校验,beforeEnter 处理某条路由的特殊规则,onBeforeRouteUpdate 处理组件复用时的参数变化,onBeforeRouteLeave 处理离开确认,afterEach 用于导航完成后的标题或埋点逻辑。
9. 路由守卫能代替后端权限校验吗
可以这样回答:
不能。前端代码和本地 token 都可能被用户修改,守卫只能阻止正常界面流程,攻击者仍然可以直接请求接口。后端必须独立验证身份和权限,前端守卫只负责用户体验和页面入口控制。
10. 路由参数改变为什么 mounted 不执行
可以这样回答:
从
/users/1到/users/2仍然匹配同一个用户详情组件,Vue Router 会复用原组件实例,因此 setup 和 mounted 不会重新执行。应该 watchroute.params.id,或使用 onBeforeRouteUpdate 响应参数变化。
11. 路由懒加载是什么
可以这样回答:
路由懒加载把路由组件写成
() => import('./Page.vue')。构建工具会进行代码分割,在第一次访问对应路由时才加载组件代码,从而减少首屏一次性下载的 JavaScript。路由组件不需要再套 defineAsyncComponent。
12. push 和 replace 有什么区别
可以这样回答:
push 会向浏览器历史栈增加一条记录,用户可以后退回来;replace 会替换当前记录,不新增历史。普通页面进入常用 push,登录完成或纠正地址时经常使用 replace。
二十八、本文知识地图
1. 路由工作流程
text
URL 变化
↓
Vue Router 匹配 routes
↓
更新当前 route
↓
RouterView 切换组件
2. 两个核心组件
text
RouterLink:发起前端路由导航
RouterView:展示匹配到的路由组件
3. 两个核心对象
text
route:path、params、query、meta
router:push、replace、back、go
4. URL 参数
text
params:资源身份和路径结构
query:分页、搜索、筛选等附加状态
5. 路由模式
text
hash:# 后的路由不发送给服务器,部署简单
history:URL 自然,但服务器需要回退 index.html
6. 守卫
text
beforeEach:全局前置判断
beforeEnter:路由独享判断
onBeforeRouteUpdate:复用组件的路由变化
onBeforeRouteLeave:离开页面前判断
afterEach:导航完成后的处理
7. 性能
text
component: () => import('./Page.vue')
→ 路由级代码分割
→ 访问时再加载
二十九、总结
Vue Router 的核心不是"切换几个组件",而是建立 URL 和页面状态之间的关系:
text
URL 决定应该展示哪个页面;
RouterLink 或 router 发起导航;
route 保存当前地址信息;
RouterView 渲染匹配到的组件。
SPA 内部跳转不重新加载整份 HTML,是因为 Vue Router 使用 hash 或 History API 维护 URL,再通过响应式路由状态驱动组件更新。
动态路由使用 params 表达资源身份,query 适合分页、搜索和筛选。相同路由组件在 params 变化时通常会被复用,因此要监听具体参数,而不能只依赖 onMounted。
history 模式的 URL 更自然,但刷新深层地址时浏览器会真正请求该路径,所以服务器必须配置回退到 index.html;前端还需要自己的兜底路由显示 404 页面。
导航守卫可以改善登录和权限页面的访问流程,但不能代替后端鉴权。路由懒加载则通过动态 import,把页面代码延迟到访问时加载。
对于秋招,最终应该能独立回答这条链路:
text
点击 RouterLink
→ URL 无刷新变化
→ Vue Router 匹配路由
→ 守卫确认导航
→ 必要时加载路由代码
→ RouterView 更新页面