别名:
作用: — 类似 【保安】
导航守卫一共有三种形式 【 项目三选一 】
A: 全局导航守卫
针对的整个项目,也就是管理或是监听整个项目
B: 路由独享守卫 ·
路由配置选项中的一个
C: 组件内守卫【 王者 】
当前组件
功能: 导航守卫可以监听路由变化情况,做路由跳转拦截
名词
关于 next 的使用
业务: 当我们进入到一个项目的首页时,但是当我们没有注册账号时,它主动跳转到了注册/登录页
router.beforeEach((to, from, next) => {
// next() // 默认true
// next( false )
// next()
// 判断用户是否登录,我们找token值 cookie localStorage
const token = localStorage.getItem("token");
if (to.path === "/home") {
next();
}
if (to.path == "/aa") {
next();
}
if (to.path === "/mine/login") {
if (token) {
// 条件成立,那么我们就可以进行页面跳转了
next("/home");
} else {
// 条件不成立,留在当前页面
next();
}
} else {
next(false);
}
});
全局导航守卫案例
import router from "./index";
// 书写全局导航守卫
// 1. 全局导航守卫 - 前置守卫 - 管理的是路由的进入
router.beforeEach((to, from, next) => {
// to表示目标路径 - 我想进哪里
// from表示当前路径 - 我现在所处的路径
// next 表示 from 到 to 的连接 ,它可以控制 是否允许进入
// 里面什么都不写表示不允许通过
// next() 表示允许通过 默认实参为true
// next() == next( true )
// next( true )
// next( false ) 表示不允许通过
// next( false )
/* 参数输出 */
// console.log('to',to)
// console.log('from',from)
// 写一个自动的跳转
// 当我们登陆了之后,我们前端要将后端给的token值存储起来,然后我们通过判断token是否存在,来进入项目
const token = localStorage.getItem("token");
// if ( token ) { //路由鉴权
// //表示token存在
// next()
// } else {
// next( false )
// }
// 如果token不存在,那么我们自动跳转登录页面,然后让用户登录,来获得token值
if (to.path == "/home") {
next();
}
if (to.path == "/category") {
next();
}
if (!token) {
// next( 路由路径 ) 可以跳转某一个路由
/* 以下这么些会出现一个问题就是死循环 */
// console.log( 1 )
// next('/mine/login')
// next()
/*
所以要想不出现死循环,我们要加判断条件
思考: 需要打破这个死循环的条件
这样,用户进入哪一个页面可以由我们来控制
*/
switch (to.path) {
case "/home":
next("/mine/login");
next();
break;
case "/category":
next("/mine/login");
next();
break;
case "/shopcar":
next("/mine/login");
next();
break;
default:
break;
}
}
next();
});
// 2. 后置守卫 - 表示的离开
router.afterEach((to, from) => {
//没有next参数,那就没有拦截作用,它可以提示作用
if (to.path == "/home") {
alert("您是否要今天入首页?");
}
});
router.beforeEach((to, from, next) => {
// to and from are both route objects.must call `next`.
// if (to.path === '/mine') return next()
// const tokenStr = window.sessionStorage.getItem('token')
// if (!tokenStr) return next('/mine')
// next()
const token = window.sessionStorage.getItem("token");
if (!token) {
if (to.path === "/home/zhy") {
next("/mine");
} else {
next();
}
} else {
next();
}
});
router.afterEach((to, from) => {
// to and from are both route objects.
if (to.path === "/recommend") {
alert("hello");
}
});
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import { Button, Tabs, TabPane } from "element-ui";
// Vue.component('el-button',Button)
import axios from "axios";
import "./router/global-config"; ////
Vue.prototype.$http = axios; // 给Vue原型绑定 axios 这样我们项目中的每一个组件都可以通过 this.$http 来使用 axios了
Vue.use(Button);
Vue.use(Tabs);
Vue.use(TabPane);
Vue.config.productionTip = false;
new Vue({
router, // 在根实例选项中注入路由
render: h => h(App)
}).$mount("#app");
路由独享守卫案例
import Home from "../pages/home";
import Category from "../pages/category";
import Recommend from "../pages/recommend";
import ShopCar from "../pages/shopcar";
import Mine from "../pages/mine";
import Error from "../pages/error";
import Zhy from "../pages/home/children/zhy.vue";
import Lsy from "../pages/home/children/lsy.vue";
import List from "../pages/list";
const token = window.localStorage.getItem("token");
const routes = [
{
path: "", //路由路径
redirect: "/home"
},
{
path: "/home", //路由路径
component: Home, //当前路由路径对应的组件
children: [
{
path: "", //这里不写‘/’
redirect: "zhy"
},
{
path: "zhy", //这里不写‘/’
component: Zhy,
name: "zhy"
},
{
path: "lsy",
component: Lsy,
name: "lsy",
alias: "lishanyu"
}
]
},
{
path: "/category", //路由路径
component: Category //当前路由路径对应的组件
},
{
path: "/recommend", //路由路径
component: Recommend //当前路由路径对应的组件
},
{
path: "/shopcar", //路由路径
component: ShopCar, //当前路由路径对应的组件
beforeEnter: (to, from, next) => {
console.log("beforeEnter");
if (token) {
next();
} else {
next(false);
}
}
},
{
path: "/mine", //路由路径
component: Mine //当前路由路径对应的组件
},
{
path: "/list/:id", //用于url携带id 和 查找字符串
component: List,
name: "list" //当前路由路径对应的组件
},
{
path: "/error", //路由路径
component: Error //当前路由路径对应的组件
}
// {
// path: '**', //错误路由匹配,vue规定这个必须放在最下面,它必须将上面的路由全找一遍,找不到才用当前这个
// redirect: '/error' //重定向
// },
];
// 导出模块
export default routes;
组件内守卫【 王者 】案例
src > pages > home > children > zhy.vue
<template>
<article>
<div>
<p>张浩雨个人页面</p>
</div>
</article>
</template>
<script>
export default {
name: "Zhy",
beforeRouteLeave (to, from, next) {
if(confirm('确定要离开么?')){
next()
}else{
next(false)
}
}
};
</script>
<style>
</style>
<template>
<article>
<div>
<p>ShopCar页面p>
<div>
购物车
<hr />
<h3>数据预载的数据渲染h3>
<ul>
<li v-for="item in categorys.data" :key="item.id">
<p>商品名称: {{ item.shop_name }}p>
<p>商品价格: {{ item.price }}p>
li>
ul>
div>
div>
article>
template>
<script>
import axios from "axios";
export default {
beforeRouteEnter(to, from, next) {
axios
.get("/mock/shopcar.json")
.then(res => {
console.log(res);
// 问题:我们想把获得的数据提前给到组件,但是我们在这个钩子中无法获得组件,this也没有
// 解决
next(vm => {
console.log("张浩雨: beforeRouteEnter -> vm", vm);
vm.$set(vm.categorys, "data", res.data);
next();
});
})
.catch(err => console.log(err));
},
name: "ShopCar",
data() {
return {
categorys: {
data: null
}
};
}
};
script>
<style lang="stylus" scoped>
ul
list-style none
style>
vm
访问组件实例this
1.下载animate.css $ yarn add animate.css -S
- main.js 引入animate.css
import Vue from 'vue'
import App from './App.vue';
import router from './router';
import axios from 'axios';
import {
Button,
TabPane,
Tabs
} from 'element-ui';
import './router/global-config';
import 'animate.css';
Vue.prototype.$https = axios;
// Vue.use( ElementUI )
Vue.use(Button);
Vue.use(TabPane);
Vue.use(Tabs);
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router //在根实例中注入路由
}).$mount('#app');
<section class="row">
<transition
mode="out-in"
enter-active-class="animated rotateIn"
leave-active-class="animated rotateOut"
>
<router-view>router-view>
transition>
上面的用法会给所有路由设置一样的过渡效果,如果你想让每个路由组件有各自的过渡效果,可以在各路由组件内使用 并设置不同的 name。
const Foo = {
template: `
...
`
}
const Bar = {
template: `
`
}
<transition :name="transitionName">
<router-view>router-view>
transition>
// 接着在父组件内
// watch $route 决定使用哪种过渡
watch: {
'$route' (to, from) {
const toDepth = to.path.split('/').length
const fromDepth = from.path.split('/').length
this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
}
}
你可以使用 router.beforeEach 注册一个全局前置守卫:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中 。
每个守卫方法接收三个参数:
to: Route: 即将要进入的目标 路由对象
from: Route: 当前导航正要离开的路由
next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
确保要调用 next 方法,否则钩子就不会被 resolved。
#全局后置钩子
router.afterEach((to, from) => {
// ...
});
const router = new VueRouter({
routes: [
{
path: "/foo",
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
});
这些守卫与全局前置守卫的方法参数是一样的。
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
};
beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,你可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了。
beforeRouteUpdate (to, from, next) {
// just use `this`
this.name = to.params.name
next()
}
这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。
beforeRouteLeave (to, from , next) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。
首先,可以将异步组件定义为返回一个 Promise 的工厂函数 (该函数返回的 Promise 应该 resolve 组件本身):
const Foo = () => Promise.resolve({ /* 组件定义对象 */ })
第二,在 Webpack 2 中,我们可以使用动态 import语法来定义代码分块点 (split point):
import('./Foo.vue') // 返回 Promise
注意
结合这两者,这就是如何定义一个能够被 Webpack 自动代码分割的异步组件。
const Foo = () => import('./Foo.vue')
在路由配置中什么都不需要改变,只需要像往常一样使用 Foo:
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用 命名 chunk,一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4)。
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
Webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中。
定义路由的时候可以配置 meta 字段:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field
meta: { requiresAuth: true }
}
]
}
]
})
那么如何访问这个 meta 字段呢?
首先,我们称呼 routes 配置中的每个路由对象为 路由记录。路由记录可以是嵌套的,因此,当一个路由匹配成功后,他可能匹配多个路由记录
例如,根据上面的路由配置,/foo/bar 这个 URL 将会匹配父路由记录以及子路由记录。
一个路由匹配到的所有路由记录会暴露为 $route 对象 (还有在导航守卫中的路由对象) 的 $route.matched 数组。因此,我们需要遍历 $route.matched 来检查路由记录中的 meta 字段。
下面例子展示在全局导航守卫中检查元字段:
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!auth.loggedIn()) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // 确保一定要调用 next()
}
})
有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:
导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。
导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。
从技术角度讲,两种方式都不错 —— 就看你想要的用户体验是哪种。