路由的概念
SPA:
- SPA (Single Page Application,单页面应用),整个应用只有一个完整的页面,所有组件的展示、隐藏都在这一个页面中完成。
- 不同组件之间的切换需要通过前端路由来实现,数据需要通过 Ajax 获取。
- 缺点:1. 首屏加载速度较慢、 2. 不利于 SEO。对应的优化方法:1. 路由懒加载、代码压缩、CDN 加速、网络传输压缩、 2. SSR 服务器端渲染。
路由:
- 后端路由:请求方式、请求地址与 function 处理函数之间的映射。根据请求的不同,执行不同的 function 对数据进行操作。
- 前端路由:hash 地址与组件之间的映射。根据 hash 地址的改变,显示、隐藏对应的组件。
- hash 地址可使用
location.hash
获取。 (eg:http://192.168.1.103:8080/?name=superman#/about
→#/about
) - hash 地址也叫锚链接,锚链接的更新不会引起页面的刷新,但会引起历史记录的变化。
Vue Router
- Vue Router 是 Vue 官方配套的插件,用于实现 SPA
- 下载时需要注意版本问题:Vue Router 4 - Vue 3、 Vue Router 3 - Vue 2
基本使用
-
下载 npm 包:
npm i vue-router
-
创建 router 配置文件 @/router/index
import Vue from 'vue';
import VueRouter from 'vue-router';
// 注册 vue-router (需在 router 实例创建之前调用)
Vue.use(VueRouter);
// 引入首页组件
import HomeView from '../views/HomeView.vue';
// 配置路由规则
const routes = [
{
path: '/', // 路由路径; 注意: hash 模式下, 路由路径要使用小写
name: 'home', // 路由名称; 可选; name 值必须保持唯一性
component: HomeView, // 展示的组件
},
{
path: '/about',
component: () => import('../views/AboutView.vue'), // 懒加载写法, 访问该组件时再获取
// 一般情况下, 除初始页面以外的其他页面都应该使用懒加载
},
];
// 创建 router 实例
const router = new VueRouter({ routes });
// 导出 router 实例
export default router;
- 在入口文件 main.js 中导入 router 实例并注册
import Vue from 'vue';
import App from './App.vue';
import router from './router'; // 导入 router 实例
// 只需导入 router 文件夹,默认就会导入该文件里面的 index.js 文件
new Vue({
router, // 将 router 实例注册到 Vue 实例中
render: (h) => h(App),
}).$mount('#app');
-
编写路由组件:我们约定 [组件] 放置在 components 文件夹中;[路由组件] 放置在 views / pages 文件夹中。
-
在 App.vue 中使用路由:1. 添加路由链接:
<router-link to="/路由路径"> XXX </router-link>
; 2. 添加路由占位符:<router-view> </router-view>
||<router-view />
<template>
<div id="app">
<nav>
<!-- 设置 [路由链接]、并设置 `to` 属性 -->
<router-link to="/"> Home </router-link>
|
<router-link to="/about"> About </router-link>
<!-- 点击 router-link 会更新路由路径, 进而渲染对应的组件 -->
</nav>
<!-- 设置 [路由占位符], 路由路径匹配到的组件会被渲染到这里 -->
<router-view />
</div>
</template>
<script>
export default { name: 'App' };
</script>
router-link
的底层其实就是a
标签,所以可以把router-link
理解为a
的升级版。- 可以给
router-link
设置 props:
active-class="XXX"
:设置路由激活时的 class 名,默认为router-link-active
exact-active-class="XXX"
:设置链接精准激活时的 class 名,默认为router-link-exact-active
- 访问当前路由,当前路由组件才会被创建;离开当前路由,当前路由组件会被销毁。
- 注册路由后,Vue 实例身上会多出 2 个属性
$route
&$router
:
①$route
:路由组件的信息对象,每个路由的$route
都不一样。一般用于获取路由信息,eg:路径、query、params…
②$router
VueRouter 的实例化对象,整个应用只有一个$router
。一般用于进行编程式导航,eg:push、replace…
路由重定向
可以通过 redirect
属性配置路由重定向。redirect
的属性值可以为 String、Object、Function:
const router = new VueRouter({
routes: [
{
path: '/',
redirect: '/user', // redirect 的值为目标路由的路由路径 path
},
{
path: '/user',
component: User,
},
],
});
{
path: '/',
redirect: { name: 'XXX' }, // redirect.name 的值为目标路由的路由名称 name
// redirect: { path: '/XXX' }, // redirect.path 的值为目标路由的路由路径 path
},
{
path: '/',
// 接收目标路由作为参数
redirect: to => {
// return '路由路径 /XXX' / return { name: '路由名称 XXX' } / return { path: '路由名称 /XXX' }
return { path: '/XXX', query: { title: to.params.searchText } },
},
},
- 相对重定向:
{
path: '/users/:id/posts',
redirect: 'profile' // 相对位置不以 / 开头
},
上例表示:从 '/users/:id/posts'
重定向到 '/users/:id/profile'
嵌套路由
- 设置路由规则:
const router = new VueRouter({
routes: [
{
path: '/home',
name: 'Home',
component: HomeView,
// 设置 children 属性添加子路由, 属性值为数组, 数组元素为子路由的配置对象
children: [
{
path: 'son', // 注意:子路由的 path 属性值不需要 `/` 前缀 !!!
name: 'Son', // 设置路由名称(可选)
component: () => import('../views/SonView.vue'),
},
],
},
],
});
- 配置组件:
编写 router-link
时,有 3 种写法:
① 直接在 router-link
标签中设置 to
属性,属性值就是路由路径
② 绑定 to
属性,以对象形式,通过组件的 name
属性值,设置路由路径
③ 绑定 to
属性,以对象形式,通过组件的 path
属性值,设置路由路径
<template>
<div id="app">
<div id="nav">
<!-- 直接写路由路径 -->
<router-link to="/home"> Home </router-link>
<!-- 绑定 to 属性,以对象的形式,通过组件的 name 属性,设置路由路径 -->
<!-- <router-link :to="{ name: 'Home' }"> Home </router-link> -->
<!-- 绑定 to 属性,以对象的形式,通过组件的 path 属性,设置路由路径 -->
<!-- <router-link :to="{ path: '/home' }"> Home </router-link> -->
</div>
<router-view />
</div>
</template>
<script>
export default { name: 'App' };
</script>
<template>
<div>
<h1>This is an Home page</h1>
<hr />
<!-- 注意: 编写路径时,路径要写全!!! -->
<router-link to="/home/son">Son</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default { name: 'HomeView' };
</script>
<template>
<div>
<h2>Son.vue</h2>
</div>
</template>
<script>
export default { name: 'SonView' };
</script>
默认子路由
- 设置子路由时,如果该子路由的
path
属性值为空字符串,则为 [默认子路由]。 - 设置默认子路由后,当前路由不能设置
name
属性,否则 Vue 会抛出警告:“进入当前路由时,可能尚未加载默认子路由”。
const routes = [
{
path: '/home',
component: HomeView,
// 设置默认子路由后, 当前路由不能设置 name 属性
children: [
{
path: '', // 子组件的 path 设置为空字符串, 表示默认显示该组件
name: 'Son',
component: () => import('../views/SonView.vue'),
},
],
},
];
别名
- 可通过
alias
属性设置别名:
const routes = [{ path: '/', component: Homepage, alias: '/home' }];
上例中,访问 /
和 /home
都会展示 Homepage
- 可以给
alias
属性传入数组作为属性名,以设置多个别名:
const routes = [
{
path: '/users',
component: UsersLayout,
children: [
{ path: '', component: UserList, alias: ['/people', 'list'] },
// 为这 3 个 URL 呈现 UserList
// - /users
// - /users/list
// - /people
],
},
];
- 如果你的路由有参数,请确保在任何绝对别名中包含它们:
const routes = [
{
path: '/users/:id',
component: UsersByIdLayout,
children: [
{ path: 'profile', component: UserDetails, alias: ['/:id', ''] },
// 为这 3 个 URL 呈现 UserDetails
// - /users/24
// - /users/24/profile
// - /24
],
},
];
捕获所有路由
Vue-Router 3 中:
如果想匹配任意路径,可以使用通配符 *
:
{
// 会匹配以 `/user-` 开头的任意路径
path: "/user-*",
},
{
// 会匹配所有路径
path: "*",
},
- 含有通配符的路由应该配置在最后面
{ path: '*' }
通常用于展示 404 页面
当使用通配符 *
时,$route.params
内会自动添加一个 pathMatch
参数。它包含了 URL 通过通配符被匹配的部分:
// 给出一个路由 { path: '/user-*' }
this.$router.push('/user-admin');
this.$route.params.pathMatch; // 'admin'
// 给出一个路由 { path: '*' }
this.$router.push('/non-existing');
this.$route.params.pathMatch; // '/non-existing'
Vue-Router 4 中:
如果想匹配任意路径,需要配置自定义正则:
{
path: "/:catchAll(.*)", // 动态路由参数 + catchAll(正则); 正则中的 `.` 为通配符; `*` 为量词, 表示 0 ~ n 个
redirect: '/404',
}
如果直接使用 *
,会报错:Catch all routes ("*") must now be defined using a param with a custom regexp
数据传递
query
query 用于:父路由给子路由传递数据
① 绑定 to
属性,以 [对象] 的形式设置 query 参数
② 绑定 to
属性,以 [字符串] 的形式设置 query 参数
<template>
<div>
<h1>This is an Home page</h1>
<ul>
<li v-for="item in arr" :key="item.id">
<!-- 绑定 to 属性,以 [对象] 的形式设置 query 参数 -->
<router-link
:to="{
// 通过组件的 name 属性,设置路由路径
name: 'Son',
// 通过组件的 path 属性,设置路由路径
// path: '/home/son',
// 通过 query 属性,传递父组件中的数据
query: { way: '对象', id: item.id, name: item.hero },
}"
>
{{ item.hero }}
</router-link>
|
<!-- 绑定 to 属性,以 [字符串] 的形式设置 query 参数 -->
<router-link
:to="`/home/son?way=字符串模版&id=${item.id}&name=${item.hero}`"
>
{{ item.hero }}
</router-link>
</li>
</ul>
<hr />
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'HomeView',
data() {
return {
arr: [
{ id: '001', hero: '卡莎' },
{ id: '002', hero: '火男' },
{ id: '003', hero: '卡萨' },
],
};
},
};
</script>
通过 $route
在子路由中获取父路由中传递过来的数据:$route.query.属性值
:
<template>
<div class="son">
<!-- 在子组件中,通过 "$route.query.属性值" 获取父组件中的数据 -->
<p>way: {{ $route.query.way }}</p>
<p>id: {{ $route.query.id }}</p>
<p>name: {{ $route.query.name }}</p>
</div>
</template>
<script>
export default { name: 'SonView' };
</script>
上例的路由配置:
const routes = [
{
path: '/home',
name: 'Home',
component: HomeView,
children: [
{
path: 'son',
name: 'Son',
component: () => import('../views/SonView.vue'),
},
],
},
];
params (动态路由参数)
除了通过 query 传参,我们还可以通过 params 传递 [动态路由参数]
此时,接收动态参数的路由配置的 path
属性值应改成如下形式:
const routes = [
{
path: '/home',
name: 'Home',
component: HomeView,
children: [
{
path: 'son/:way/:id/:name', // 设置动态路由
name: 'Son',
component: () => import('../views/SonView.vue'),
},
],
},
];
- 父路由通过 [对象] / [字符串] 的形式传参。
- 注意:通过
params
接收数据时:如果使用 [对象] 写法,必须设置name
属性 !!!
<template>
<div>
<h1>This is an Home page</h1>
<ul>
<!-- 遍历所有的数据 -->
<li v-for="item in arr" :key="item.id">
<!-- 绑定 to 属性,以 [对象] 的形式设置 params 参数 -->
<router-link
:to="{
// 必须设置 name 属性
name: 'Son',
// 通过 params 属性,传递父组件中的数据
params: { way: '对象', id: item.id, name: item.hero },
}"
>
{{ item.hero }}
</router-link>
|
<!-- 绑定 to 属性,以 [字符串] 的形式设置 params 参数 -->
<router-link
:to="`/home/son/字符串模版/${item.id}/${item.hero}`"
>
{{ item.hero }}
</router-link>
</li>
</ul>
<hr />
<router-view></router-view>
</div>
</template>
子路由通过 $route.params.属性值
获取动态参数:
<template>
<div>
<!-- 在子组件中,通过 "$route.params.属性值" 获取父组件中的数据 -->
<p>way: {{ $route.params.way }}</p>
<p>id: {{ $route.params.id }}</p>
<p>name: {{ $route.params.name }}</p>
</div>
</template>
- 在 hash 地址中,
/
后面的参数叫 [路径参数];?
后面的参数叫 [查询参数]。 - [路径参数] 可通过
this.$route.params
获取; [查询参数] 可通过this.$route.query
获取。 - 可通过
this.$route.path
获取 hash 地址 + [路径参数]。 可通过this.$route.fullPath
获取 hash 地址 + [路径参数] + [查询参数]。
如何设置 params 参数可传可不传?
在动态路由后面添加 ?
:
path: 'son/:msg?', // 动态路由
此时,如果你传了参数,且参数为空字符串 ""
,Vue 会抛出警告,说不能传递空字符串。
props
在 router 文件中设置 props
属性,可较方便地在路由中接收参数。
方式 1:属性值为【对象】(额外传递一些数据)
该对象中的 key-value 都会传递给子路由,子路由组件通过 props
接收参数:
const routes = [
{
path: '/home',
name: 'Home',
component: HomeView,
children: [
{
path: 'son/:way/:id/:name',
name: 'Son',
component: () => import('../views/SonView.vue'),
// 方法 1: props 对象, 传递属性及其属性值
props: { msg: 'props 中的数据' },
},
],
},
];
<template>
<div>
<p>way: {{ $route.params.way }}</p>
<p>id: {{ $route.params.id }}</p>
<p>name: {{ $route.params.name }}</p>
<!-- 在 template 标签中直接使用 -->
<p>{{ msg }}</p>
</div>
</template>
<script>
export default {
name: 'SonView',
props: ['msg'], // 在组件中设置 props 属性接收数据
};
</script>
方式 2:属性值为【布尔值】(只能传递 params 参数)
所有的 params 数据都会以 props 形式传递:
// 方法 2: 布尔值,表示父路由的所有 params 数据都通过 props 传递
props: true;
<template>
<div>
<!-- 在 template 标签中直接使用 -->
<p>way: {{ way }}</p>
<p>id: {{ id }}</p>
<p>name: {{ name }}</p>
</div>
</template>
<script>
export default {
name: 'SonView',
props: ['way', 'id', 'name'], // 在组件中设置 props 属性接收数据
};
</script>
方式 3:属性值为【函数】 [最常用](可以传递 params、query 参数)
函数返回一个对象,对象的 key-value 都会以 props 的形式传递给组件:
// 方法 3:props 方法
props(route) { // 接收 1 个参数,为 $route 对象
return {
id: route.params.id,
name: route.params.name,
way: route.params.way,
}
}
这里可以使用 [解构赋值的连续写法]:
props({ params: { id, name, way } }) {
return { id, name, way };
},
响应路由参数的变化
当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用
复用组件时,想对路由参数的变化作出响应的话,你可以简单地使用 watch 侦听 $route
对象:
watch: {
$route(to, from) {
// 对路由变化作出响应...
},
},
或者使用 beforeRouteUpdate
导航守卫:
beforeRouteUpdate(to, from, next) {
// react to route changes...
// don't forget to call next()
},
注意:若 Vue Router 跳转前后使用的是同一个组件,Vue 会直接复用,这会导致 URL 更新后页面没有重新渲染。此时可以给 router-link
添加唯一标识 key
:<router-view :key="$route.fullPath" />
编程式路由
至此,上面的 demo 使用的都是 [声明式导航]。下面介绍的是 [编程式导航]。
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';
Vue.use(VueRouter);
const routes = [
{
path: '/home',
name: 'Home',
component: Home,
children: [
{
path: 'son', // 静态路由
name: 'Son',
component: () => import('../views/Son.vue'),
props(route) {
// 接收 $route 对象作为第 1 参数
return {
// 这里通过 query 获取数据
id: route.query.id,
name: route.query.name,
way: route.query.way,
};
},
},
],
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue'),
},
];
const router = new VueRouter({ routes });
export default router;
push & replace
this.$router.push("指定路由地址")
:跳转到指定路由地址
<template>
<div id="app">
<div id="nav">
<router-link to="/home">Home</router-link> |
<router-link :to="{ name: 'About' }">About</router-link> |
<button @click="jump">jump Son.vue</button>
</div>
<router-view />
</div>
</template>
<script>
export default {
name: 'App',
methods: {
jump() {
this.$router.push('/home/son');
},
},
};
</script>
使用 push
跳转,会产生新的历史记录;使用 replace
跳转,会替换当前历史记录,即不会产生新的历史记录。
this.$router.replace('/myself');
也可以给 router-link 设置 replace
属性,实现等效的路由跳转:
<router-link replace to="/myself"> 跳转到... </router-link>
跳转的同时传递数据:
String 形式:
// 传递 query 数据
this.$router.push('/home/son?way=push&id=01&name=superman');
// 传递 params 数据
this.$router.push('/home/son/push/01/superman');
Object 形式:
this.$router.push({
name: 'Son', // 配置 name 属性
// 传递 query 数据
query: { way: 'push', id: '01', name: 'superman' },
// 传递 params 数据
params: { way: 'push', id: '01', name: 'superman' },
});
注意:需要传递 params 参数时,必须配置 name
属性,不能只配置 path
属性!!
跳转历史记录
this.$router.forward()
前进this.$router.back()
后退this.$router.go(num)
:num
> 0,则前进;num
< 0,则回退
<template>
<div id="app">
<div id="nav">
<router-link to="/home">Home</router-link> |
<router-link :to="{ name: 'About' }">About</router-link> |
<button @click="jump">jump Son.vue</button> |
<button @click="forward">前进</button> |
<button @click="back">后退</button>
</div>
<router-view />
</div>
</template>
<script>
export default {
name: 'App',
methods: {
jump() {
this.$router.replace({
name: 'Son',
query: { way: 'push', id: '01', name: 'superman' },
});
},
forward() {
this.$router.forward();
},
back() {
this.$router.back();
},
},
};
</script>
重复路由的报错
使用编程式导航时,如果重复点击相同的导航,会抛出错误!声明式导航没有这种问题,因为声明式导航的底层源码中已经将该问题解决了~
首先,我们需要知道会造成这种问题的原因:push
方法其实接收 3 个参数 location、resolve、reject,其中 resolve、reject 是回调函数,分别在 push [成功]、[失败] 时调用。
既然我们已经知道了 reject 是 push 失败时调用的方法,那我们手动传入 reject 回调函数,即可解决该问题:
this.$router.push(
{
name: 'Search',
params: { keyword: this.keyword },
query: { KEYWORD: this.keyword.toUpperCase() },
},
() => {},
() => {},
);
但是,这样治标不治本,岂不是每次写 push 都得传入两个回调函数?好麻烦就是说,所以我们可以直接重写 push 方法:
首先我们需要知道,push 方法不是路由实例 VueRouter 的方法,是其原型对象上的方法。
我们打开 router 文件,在创建路由实例之前,重写 push 方法:
// 保存原 push 方法
const originPush = VueRouter.prototype.push;
// 重写 push 方法
VueRouter.prototype.push = function (location, resolve, reject) {
if ((resolve, reject)) {
// if 的连续写法
originPush.call(this, location, resolve, reject);
} else {
originPush.call(
this,
location,
() => {},
() => {},
);
}
};
replace 方法同上:
// 保存原 replace 方法
const originReplace = VueRouter.prototype.replace;
// 重写 replace 方法
VueRouter.prototype.replace = function (location, resolve, reject) {
if ((resolve, reject)) {
// if 的连续写法
originReplace.call(this, location, resolve, reject);
} else {
originReplace.call(
this,
location,
() => {},
() => {},
);
}
};
导航守卫
全局导航守卫
全局前置守卫 router.beforeEach((to, from, next) => {})
to
- 即将要进入的目标路由对象from
- 正要离开的路由对象next
- 放行函数;不调用则会一直卡在这里,无法执行后面的函数
- 触发的时间:① 初始化时触发、 ② 路由切换之前触发
- 当一个导航触发时,全局前置守卫按照创建顺序调用
- 放行函数
next
有 4 种使用方式:next()
- 放行全部路由next(指定路由)
- 跳转到到指定路由
;指定路由
可以为路径"/xxx"
、{ path: "/" }
、{ name: "routerName" }
;如果为 Object,还可以设置replace: true
next(false)
- 取消当前的导航;URL 地址会重置到from
路由对应的地址next(Error 实例)
- 终止导航,错误会被传递给router.onError()
注册过的回调
// 全局前置守卫,任何路由进入之前触发
router.beforeEach((to, from, next) => {
console.log('to', to);
console.log('from', from);
next(); // 放行
});
全局后置钩子 router.afterEach((to, from) => {})
to
- 即将要进入的目标路由对象from
- 正要离开的路由对象
- 触发时间:① 初始化时触发、 ② 路由离开后触发
- 一般用于分析、更改页面标题、声明页面等辅助功能…
// 全局后置守卫,任何路由离开后触发
router.afterEach((to, from) => {
console.log('to', to);
console.log('from', from);
document.title = to.name; // 修改页面标题
});
路由独享守卫
- 路由独享守卫
beforeEnter: (to, from, next) => {}
:在指定路由规则中编写;路由进入之前触发 beforeEnter
守卫只有从一个不同的路由导航时才会触发。例如,从 /users/2 进入到 /users/3 时是不会触发的。
const routes = [
{
path: '/home',
name: 'Home',
component: Home,
children: [
{
path: 'son',
name: 'Son',
component: () => import('../views/Son.vue'),
// 指定路由守卫
beforeEnter: (to, from, next) => {
console.log('to', to);
console.log('from', from);
next(); // 放行
},
},
],
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue'),
},
];
- 需要设置多个路由独享守卫
beforeEnter
时,可以将函数数组作为属性值传递进去
function removeQueryParams(to) {
if (Object.keys(to.query).length) {
return { path: to.path, query: {}, hash: to.hash };
}
}
function removeHash(to) {
if (to.hash) {
return { path: to.path, query: to.query, hash: '' };
}
}
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: [removeQueryParams, removeHash],
},
{
path: '/about',
component: UserDetails,
beforeEnter: [removeQueryParams],
},
];
组件内的守卫
beforeRouteEnter(to, from, next) {}
:路由进入时触发beforeRouteLeave(to, from, next) {}
:路由离开时触发beforeRouteUpdate(to, from, next) {}
:路由复用时触发
- 通过路由规则 [进入] / [离开] 该组件时,触发组件守卫
- 在组件的 script 标签中设置
<script>
export default {
name: 'Son',
props: ['way', 'id', 'name'],
beforeRouteEnter(to, from, next) {
console.log('路由进入时触发');
console.log('to', to);
console.log('from', from);
next();
},
beforeRouteLeave(to, from, next) {
console.log('路由离开时触发');
console.log('to', to);
console.log('from', from);
next();
},
beforeRouteUpdate(to, from, next) {
console.log('路由组件复用时触发');
console.log('to', to);
console.log('from', from);
next();
},
};
</script>
beforeRouteEnter
方法中,无法直接获取 this
对象,因为 “路由进入前” 组件尚未被创建。此时,我们可以通过 next
的回调函数,在 beforeRouteEnter
方法中获取 this
:
beforeRouteEnter(to, from, next) {
console.log("路由进入之前触发", this); // 这里的 this 为 undefined
next((vm) => { // 通过 next 的回调函数获取 this
console.log("vm", vm);
});
},
注意:beforeRouteEnter
是支持给 next
传递回调的唯一守卫。
beforeRouteLeave
通常用来预防用户在还未保存修改前突然离开。该导航可以通过返回 false
来取消。
完整的导航解析流程
- 导航被触发
- 在失活的组件里:触发组件守卫
beforeRouteLeave(to, from, next) {}
- 触发全局前置守卫
router.beforeEach((to, from, next) => {})
- 在重用的组件里:触发组件守卫
beforeRouteUpdate(to, from, next) {}
- 在路由配置里:触发路由独享守卫
beforeEnter: (to, from, next) => {}
- 解析异步路由组件
- 在被激活的组件里:触发组件守卫
beforeRouteEnter(to, from, next) {}
- 导航被确认
- 触发全局后置守卫
router.afterEach((to, from) => {})
- 触发 DOM 更新
- 在被激活的组件里:触发组件守卫
beforeRouteEnter(to, from, next) {}
中传给next
的回调函数,[创建好的组件实例] 会作为 [回调函数的参数] 传入
设置缓存
- 组件不用时,默认会被销毁
- 可以使用 Vue 内置的
keep-alive
标签包裹路由出口router-view
,此时,在该路由出口显示的所有路由都不会被销毁。
<keep-alive> <router-view /> </keep-alive>
- 我们可以设置
include="组件名"
,此时只有指定组件会被缓存。注意:include
的属性值是组件名!!!
<keep-alive include="Home"> <router-view /> </keep-alive>
- 当需要缓存多个组件时,我们可以绑定
include
属性,并传入一个数组参数:
<keep-alive :include="['Home', 'About']"> <router-view /> </keep-alive>
- 我们也可以设置
exclude
属性,表示除了指定组件,其他组件都会被缓存。书写格式与include
一样。
<keep-alive exclude="Home"> <router-view /> </keep-alive>
钩子函数:
activated() {}
keep-alive 缓存的组件激活时调用deactivated() {}
keep-alive 缓存的组件失活时调用
activated() {
console.log("进入缓存组件");
},
deactivated() {
console.log("离开缓存组件");
},
缓存机制:
因为 keep-alive 有缓存机制,只有第一次打开该路由时会创建 (beforeCreate、created、beforeMount、mounted、activated),再进入时只会激活 activated
设置滚动
在创建 Router 实例时,我们可以通过 scrollBehavior
方法设置滚动,使页面跳转到新路由时,会滚动到指定位置:
// 创建 VueRouter 实例
const router = new VueRouter({
// ...
// 设置滚动行为 scrollBehavior
scrollBehavior(to, from, savedPosition) {
// `y: 0` 表示滚动条在最上面
// `behavior: 'smooth'` 可以让滚动变得流畅
return { y: 0, behavior: 'smooth' };
},
});
关于 scrollBehavior
的第 3 个参数 savedPosition
:
只有当这是一个 popstate
导航时才可用。当 history 对象发生变化时,就会触发 popState 事件。可以通过 popState 的事件活动对象的 state 属性访问当前历史记录的状态对象的拷贝。
会触发 popstate
事件的场景:① 用户点击浏览器的前进、后退按钮;② 代码中调用 history.back()
、history.forward()
、history.go()
;③ a
标签的锚点。
注意:当网页加载时,各浏览器对 popstate
事件是否触发有不同的表现:Chrome 和 Safari 会触发 popstate
事件,而 Firefox 不会。
- 返回值:
{ x: number, y: number }
/savedPosition
scrollBehavior(to, from, savedPosition) {
if (savedPosition){
// 直接返回 savedPosition,在按下 [后退] / [前进] 按钮时,就会像浏览器的原生表现那样
return savedPosition;
} else {
return { y: 0 };
}
}
滚动到锚点:
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return { selector: to.hash };
}
}
延迟滚动:
scrollBehavior(to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ x: 0, y: 0 });
}, 500);
});
}
路由元信息
- 用于将任意信息附加到路由规则上。eg:过渡名称、路由访问权限…
- 可通过配置
meta
属性来设置路由元信息。可以在导航守卫上访问meta
属性值。
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';
Vue.use(VueRouter);
const routes = [
{
path: '/home',
name: 'Home',
component: Home,
meta: { name: 'myHome' }, // 设置 meta 属性
children: [
{
path: 'son',
name: 'Son',
component: () => import('../views/Son.vue'),
props(route) {
return {
id: route.query.id,
name: route.query.name,
way: route.query.way,
};
},
meta: { name: 'mySon' }, // 设置 meta 属性
},
],
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue'),
meta: { name: 'myAbout' }, // 设置 meta 属性
},
];
const router = new VueRouter({ routes });
// 全局后置守卫,路由离开后触发
router.afterEach((to, from) => {
document.title = to.meta.name; // 获取 meta 中的数据
});
export default router;
mode
hash
模式:
- 地址中带着
#
- 使用 hash 地址 模拟完整的 URL;当 hash 地址更新时,页面不会重新加载
- hash 值不会包含在 HTTP 请求中,即 hash 值不会带给服务器
- 如果将地址通过第三方手机 app 分享,若 app 校验严格,则会将地址标记为不合法
history
模式:
const router = new VueRouter({ mode: 'history', routes });
- 地址中没有
#
- history 模式下,
router-link
会守卫点击事件,让浏览器不再重新加载页面 - history 模式下配置
base
选项后,所有的to
attribute 都不需要写 [基路径] - 有兼容问题,需要后台配置支持
解决 history 模式下的兼容问题:
- 后端人员一个一个地配置路由
- 在服务器上添加一个简单的回退路由。如果 URL 匹配不到任何静态资源,则提供
index.html
页面 - 后端人员通过插件 connect-history-api-fallback 设置路由(基于 node.js 的 express)
①npm i connect-history-api-fallback
、 ② 配置 connect-history-api-fallback
const express = require('express');
const app = express();
app.listen(3000);
// 配置 connect-history-api-fallback, 否则页面刷新后无法正常显示
const history = require('connect-history-api-fallback');
app.use(history());
app.use(express.static('./public'));
面试题
路由传递参数时(对象写法),path
是否可以结合 params
参数一起使用?
答:路由跳转传参时,对象写法不可以只使用 path
属性,否则不会跳转
如何配置 params
参数可传可不传? 配置好后,如果避免传递空字符串?
答:在 params 参数后面添加 ?
即可;可以使用短路算法避免传递空字符串:<router-link :to="{ name: 'Son', params: { msg: '' || null } }"> router </router-link>
路由组件能不能传递 props
数据?
答:可以;可以是 [布尔值]、[对象]、[函数]