SPA:
路由:
location.hash
获取。 (eg: http://192.168.1.103:8080/?name=superman#/about
→ #/about
)下载 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;
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. 添加路由链接:
; 2. 添加路由占位符:
||
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
$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
属性值,设置路由路径
This is an Home page
Son
Son.vue
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 用于:父路由给子路由传递数据
① 绑定 to
属性,以 [对象] 的形式设置 query 参数
② 绑定 to
属性,以 [字符串] 的形式设置 query 参数
This is an Home page
-
{{ item.hero }}
|
{{ item.hero }}
通过 $route
在子路由中获取父路由中传递过来的数据:$route.query.属性值
:
way: {{ $route.query.way }}
id: {{ $route.query.id }}
name: {{ $route.query.name }}
上例的路由配置:
const routes = [
{
path: '/home',
name: 'Home',
component: HomeView,
children: [
{
path: 'son',
name: 'Son',
component: () => import('../views/SonView.vue'),
},
],
},
];
除了通过 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
属性 !!!
This is an Home page
-
{{ item.hero }}
|
{{ item.hero }}
子路由通过 $route.params.属性值
获取动态参数:
way: {{ $route.params.way }}
id: {{ $route.params.id }}
name: {{ $route.params.name }}
/
后面的参数叫 [路径参数];?
后面的参数叫 [查询参数]。this.$route.params
获取; [查询参数] 可通过 this.$route.query
获取。this.$route.path
获取 hash 地址 + [路径参数]。 可通过 this.$route.fullPath
获取 hash 地址 + [路径参数] + [查询参数]。如何设置 params 参数可传可不传?
在动态路由后面添加 ?
:
path: 'son/:msg?', // 动态路由
此时,如果你传了参数,且参数为空字符串 ""
,Vue 会抛出警告,说不能传递空字符串。
在 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 中的数据' },
},
],
},
];
way: {{ $route.params.way }}
id: {{ $route.params.id }}
name: {{ $route.params.name }}
{{ msg }}
方式 2:属性值为【布尔值】(只能传递 params 参数)
所有的 params 数据都会以 props 形式传递:
// 方法 2: 布尔值,表示父路由的所有 params 数据都通过 props 传递
props: true;
way: {{ way }}
id: {{ id }}
name: {{ name }}
方式 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
:
至此,上面的 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;
this.$router.push("指定路由地址")
:跳转到指定路由地址
使用 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,则回退
使用编程式导航时,如果重复点击相同的导航,会抛出错误!声明式导航没有这种问题,因为声明式导航的底层源码中已经将该问题解决了~
首先,我们需要知道会造成这种问题的原因: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) {}
:路由复用时触发
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) => {})
beforeRouteEnter(to, from, next) {}
中传给 next
的回调函数,[创建好的组件实例] 会作为 [回调函数的参数] 传入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);
});
}
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;
hash
模式:
#
history
模式:
const router = new VueRouter({ mode: 'history', routes });
#
router-link
会守卫点击事件,让浏览器不再重新加载页面base
选项后,所有的 to
attribute 都不需要写 [基路径]解决 history 模式下的兼容问题:
index.html
页面npm i connect-history-api-fallback
、 ② 配置 connect-history-api-fallbackconst 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 参数后面添加 ?
即可;可以使用短路算法避免传递空字符串:
路由组件能不能传递 props
数据?
答:可以;可以是 [布尔值]、[对象]、[函数]