浙大儿院
62.44MB · 2025-11-20
Vue-router 是 Vue.js 的官方路由,是 Vue.js 构建单页应用的路由解决方案。每一个页面对应一个路由,它通过路由表,维护每个路由和组件的对应关系,在切换路由时,使得之前的组件失活并且渲染激活新对应的组件,实现单页应用的流畅切换,而无需刷新页面,提高了用户体验和性能。
所谓单页应用(Single Page Application, SPA),就是整个应用只有一个 html 页面,即index.html,html 加载后通过 javascript 动态创建和渲染内容,用户点击时,通过监听客户端路由变化去局部更新 DOM,而无需刷新页面就能达到页面切换的目的。
优点:
缺点:
与 SPA 对应是多页应用 (Multi Page Application, MPA),每个页面都有独立 html 页面,每次用户导航时会向服务器发起请求拿到对应的 html 页面。
优点:
缺点:
使用冒号(:)定义参数,比如 { path: '/user/:id', component: User }, 组件内部通过 $route.params.id 获取对应参数。
const routes = [
{ path: '/user/:id', component: () => import('./User.vue') }
];
但需要注意获取到的 id 是 string 类型,如需转成 Number 需可以在组件内部调用 Number(this.$route.params.id) 进行转换,更方便的方式是在路由中将 id 定义为 props。
const routes = [
{
path: '/user/:id',
component: UserComponent,
props: router => ({
id: Number(router.params.id),
}),
}
];
组件内部直接通过 props 接收即可。
直接通过路由配置中的 children 属性定义即可。
const routes = [
{
path: '/user',
component: UserComponent,
children: [
{ path: 'list', component: UserListComponent },
{ path: ':id', component: UserDetailComponent },
]
}
];
这样就定义了/user/list、/user/:id 两个子路由。
需要注意的是,如果子路由 path 以 / 开头,则子路由最终的 URL 将不会带上父路由的 path。
const routes = [
{
path: '/user',
component: UserComponent,
children: [
{ path: '/list', component: UserListComponent },
{ path: '/:id', component: UserDetailComponent },
]
}
];
这样定义的两个路由是 /list 和 /:id 。
直接通过 import() 方法引入路由组件即可实现路由懒加载,这样定义路由后,webpack 等打包工具在构建时就不会把这个组件打包到主包里面,而是放在一个单独的 chunk,用到时在通过 http 请求去动态加载。
const routes = [
{
path: '/user/:id',
component: () => import('./User.vue'),
}
];
export default {
props: { id: Number }
}
path 属性可以支持通配符,在路由配置末尾配置 { path: '*', component: NotFoundComponent } 即可。
钩子函数种类有: 全局守卫、路由独享守卫、组件守卫。
router.beforeEach(导航前)、router.beforeResolve(解析前)、router.afterEach(导航后)beforeEnter。beforeRouteEnter(进入组件前,无法访问通过 this 拿到组件实例)、beforeRouteUpdate(路由改变,该组件被复用时调用)、beforeRouteLeave(离开当前组件)。这些钩子在实际业务开发中,可以实现权限验证、数据预加载、进度条显示等功能。
钩子的执行顺序如下:
beforeRouteLeave 守卫。beforeEach 守卫。beforeRouteUpdate 守卫 (2.2+)。beforeEnter 守卫。import() 导入的组件)。beforeRouteEnter。beforeResolve 守卫 (2.5+)。afterEach 钩子。DOM 更新。beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。使用 URL 的 # 号(如 /home#about),浏览器不会向服务器发送请求,兼容性好,但 URL 不美观。
使用 HTML5 History API(如 /home),URL 更简洁,但需要服务器配置支持(否则刷新页面会 404),因为浏览器会向服务器请求该资源。
原理:利用 URL 的 # 号变化页面不刷新的特性,然后通过监听 hashchange,拿到 # 号后面的 哈希内容作为路由 path,进行从路由表中找到对应的组件进行激活渲染即可。
我这里用 hash.html 文件写一个简单的 demo:
<!-- hash.html -->
<ul>
<li><a href="#/home">首页</a></li>
<li><a href="#/about">关于我们页面</a></li>
</ul>
<div id="routeView">
<!-- 模拟 <router-view> 组件渲染 -->
</div>
<script>
const routes = [
{
path: '#/home',
component: '首页',
},
{
path: '#/about',
component: '关于我们页面',
}
]
const routeViewEl = document.querySelector('#routeView');
const onHashChange = () => {
for(let i = 0; i < routes.length; i++) {
if(routes[i].path === location.hash) {
routeViewEl.innerHTML = routes[i].component;
break;
}
}
}
window.addEventListener('hashchange', onHashChange);
window.addEventListener('DOMContentLoaded', onHashChange); // 首次加载时匹配路由内容
</script>
HTML5 History API 提供了以下方法:
另外在 window 对象上提供了 popstate 事件,当激活同一文档中不同的历史记录条目时会被触发。所以用 HTML5 History API + popstate 事件即可实现 history 路由。
我这里用 history.html 文件写一个简单的 demo:
<!-- history.html -->
<ul>
<li><a href="/home">首页</a></li>
<li><a href="/about">关于我们页面</a></li>
</ul>
<div id="routeView">
<!-- 模拟 <router-view> 组件渲染 -->
</div>
<script>
const routes = [
{
path: '/home',
component: '首页',
},
{
path: '/about',
component: '关于我们页面',
}
]
const routeViewEl = document.querySelector('#routeView');
const onPopState = () => {
for(let i = 0; i < routes.length; i++) {
if(routes[i].path === location.pathname) {
routeViewEl.innerHTML = routes[i].component;
break;
}
}
}
window.addEventListener('popState', onPopState)
window.addEventListener('DOMContentLoaded', () => {
const links = document.querySelectorAll('a');
links.forEach((a) => {
a.addEventListener('click', (e) => {
e.preventDefault() // 阻止 a 标签跳转
history.pushState(null, '', a.getAttribute('href')); // 获取 a 标签上的 href 属性作为跳转 URL
onPopState();
})
})
});
</script>
这里有两个注意点:
history.html 需要起一个本地服务器去预览,比如http://127.0.0.1:5500/history.html,要不然会报错 Uncaught SecurityError: Failed to execute 'pushState' on 'History': A history state object with URL 'file:///home' cannot be created in a document with origin 'null' and URL。这应该是浏览器在 History API 所做的一些安全策略。如果你使用的是 Vue Router 的 history 模式。刷新时会向服务端发起请求,服务端无法响应到对应的资源,所以会出现 404 问题。需要在服务端进行处理将所有请求重定向到你的 Vue 应用的入口文件。
配置后服务端不会再出现 404 问题,Vue 应用中要覆盖所有的路由情况。
nginx.conf 配置方式:
location / {
try_files $uri $uri/ /index.html;
}
比如路由从 /user/1 跳转到 /user/2,发现组件中 created、mounted 等生命周期钩子没有重新执行,导致没拿到最新的数据渲染到页面上。
$route 是 vue-router 注入到组件中的一个响应式数据,当路由发生变化时,其数据会发生变化。
export default {
watch: {
'$route'(to, from) {
if (to.params.id !== from.params.id) {
this.users = await axios.get(`/api/user/${this.$route.params.id}`); // 获取最新数据
}
}
}
}
在当前路由改变,但是该组件被复用时调用,vue-router 内部会调用用户在组件内定义的 beforeRouteUpdate 钩子。但需要注意,需要将 beforeRouteUpdate 定义在路由根组件才能生效。
export default {
async beforeRouteUpdate(to, from, next) {
this.users = await axios.get(`/api/user/${this.$route.params.id}`); // 获取最新数据
next();
}
}
router-view 上可以使用 key 属性,key 一旦发生变化,router-view 渲染的组件也会重新渲染。
<template>
<router-view :key="$route.fullPath"></router-view>
</template>
以上是整理的 Vue-router 的高频面试题,如有错误或者可以优化的地方欢迎评论区指正,后续还会其它前端相关面试题。
前端高频面试题之Vue(初、中级篇)
前端高频面试题之Vue(高级篇)
前端高频面试题之Vuex篇