首页 > 编程笔记 > JavaScript笔记 阅读:36

Vue路由守卫详解(附带实例)

路由守卫是路由中非常重要的一个知识点,其主要分为路由全局守卫、路由独享守卫和路由组件内守卫 3 种不同的守卫模式,利用这 3 种不同的路由守卫,可以将项目的功能打造得更加丰富,使项目的安全性更高。

本节通过 4 个部分对路由守卫进行讲解。

Vue路由全局守卫实现页面切换时对进度条的控制

到目前为止,读者已经对 vue-router 路由功能的理解越来越深入,也可以感受到 vue-router 路由功能的丰富性。不过用户的需求总是多样的:比如在进行路由切换时,是否可以设置一个进度条来增强用户体验感呢?

当利用路由切换到一个目标页面时,在这个目标页面中可能要操作的内容非常多,比如变量的定义、逻辑的操作、数据的请求及内容的渲染等,这将耗费一定的时间,而用户并不知道当前的进度,只能在页面上等待。那么在这个过程中,如果没有友好提示(比如进度条这样的页面应用),就会造成应用程序用户体验感下降的问题。

但是,如果对每个路由切换都进行进度条的显示与隐藏的控制,则将非常耗费时间并且会增加代码后期维护的难度。因此路由模块是否能提供一些全局控制的操作就显得尤为重要,此时开发者可以使用 vue-router 模块中的全局守卫函数来实现类似的功能。

vue-router 模块中的全局守卫函数主要包括:
router.beforeEach 与 router.beforeResolve 这两个函数都有 3 个参数,分别是 to、from 和 next:
当然 router.beforeResolve 与 router.beforeEach 也有不同之处,虽然 router.beforeResolve 和 router.beforeEach 都是在每次导航切换时被触发,但是 router.beforeResolve 需要确保导航被确认,以及所有路由组件内守卫和异步路由组件被解析后,才会被正确调用,而 router.beforeEach 则不会理会路由组件内守卫和异步路由组件的解析操作。

需要注意的是,router.afterEach 函数的参数只有两个,分别是 to 与 from。因为此时路由切换已经完成,不再需要确认跳转到哪个页面地址了,所以不再需要 next 参数。

既然路由的全局守卫函数能够实现路由切换的全局控制,那么上面所说的页面切换时进度条的显示与隐藏控制应该如何实现呢?

下面分 3 步实现:
1) 安装进度条模块。执行下方命令:
npm install nprogress --save

2) 在路由配置文件(router/index.js)中引入进度条与进度条样式模块内容:
import NProgress from 'nprogress'; // 引入进度条
import 'nprogress/nprogress.css'; // 引入进度条样式

3) 在利用 createRouter 创建 router 路由对象后,利用 router.beforeEach 和 router.afterEach 函数进行进度条的显示与隐藏控制:
// 注册全局前置守卫函数,在每个路由进入前执行
router.beforeEach((to, from, next) => {
    // 显示进度条
    NProgress.start();
    // 放行进入下一个路由页面
    next();
});
// 注册全局后置守卫函数,在路由切换完成之后执行
router.afterEach(() => {
    // 隐藏进度条
    NProgress.done();
});
现在刷新页面,在切换任何页面时,都会看到页面顶部出现一个进度条,而在成功切换页面后,进度条又会自行消失,如下图所示。


图 1 在切换页面时出现进度条

Vue路由全局守卫实现授权页面的禁用与指定页面的查看功能

如果利用路由全局守卫可以快速实现路由切换时的页面改善,利用路由全局守卫实现授权页面的禁用和指定页面的查看功能,就变得更为实用了。

当一个项目需要根据用户是否登录,或者用户是否拥有一定权限指定页面的禁用与查看操作时,应该如何实现呢?随着路由页面不断增多,我们不可能对每个路由页面进行逐一判断,更好的方式是进行全局统一管理,此时路由全局守卫就可以再次发挥作用了。

想要实现这一目标,我们可以在头部导航中添加一个下拉菜单,并且在下拉菜单中只放置“用户登录”与“退出登录”两个菜单项。在点击菜单项时,尝试利用本地存储的方式进行是否登录标识 loggedin 的存储与移除操作。

修改后的 components/Header.vue 文件代码如下:
<template>
  <header
    class="d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom"
  >
    ...
    ...
    <div class="dropdown text-end">
      <a
        href="#"
        class="d-block link-dark text-decoration-none dropdown-toggle"
        data-bs-toggle="dropdown"
        aria-expanded="false"
      >
      </a>
      <img
        src="@/assets/logo.png"
        alt="mdo"
        width="32"
        height="32"
      />
    </a>
    <ul class="dropdown-menu text-small">
      <li>
        <a href="#" class="dropdown-item" @click="login">用户登录</a>
      </li>
      <li>
        <a href="#" class="dropdown-item" @click="logout">退出登录</a>
      </li>
    </ul>
  </div>
</header>
</template>

<script setup>
const login = () => {
  localStorage.setItem('loggedin', true);
};

const logout = () => {
  localStorage.removeItem('loggedin');
};
</script>

但是想让 bootstrap 的 dropdown 下拉菜单起作用,还需要引入 bootstrap 的 bundle.js 打包资源文件,我们需要先在 index.html 主页面中引入 bootstrap.bundle.min.js 的脚本链接。
...
...
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
...
...

然后回到路由配置文件中,给路由全局守卫的全局前置守卫函数添加功能,主要是从本地缓存中获取用户是否已经登录的标识 loggedin,利用条件判断确认目标路由路径。如果用户还没有登录,则利用 next 函数将路由地址重新定位到首页。用户只有在已经登录的情况下才可以继续访问用户页等其他页面内容。

修改后的 router/index.js 文件代码如下:
...
...
// 注册全局前置守卫函数,在每个路由进入前执行
router.beforeEach(async (to, from, next) => {
  // 在每个路由进入前显示进度条
  NProgress.start();

  // 从本地缓存中获取用户是否已经登录的标识
  const loggedin = localStorage.getItem('loggedin');
  // 利用条件判断确认目标路由路径是否等于首页地址
  // 并且如果用户还没有登录,则除首页外,不能访问其他页面
  if (to.path !== '/' && !loggedin) {
    next({ path: '/' });
    NProgress.done();
  } else {
    // 如果用户已经登录,则可以继续访问其他页面
    next();
  }
});
...
...
现在测试页面可以发现,只有点击下拉菜单中的“用户登录”菜单项后,才能正常访问用户页,否则应用将会强制停留在首页中。如果用户点击下拉菜单中的“退出登录”菜单项,那么应用将直接返回首页。

Vue利用路由独享守卫确认页面来源

在学习完路由全局守卫后,读者还需要了解路由独享守卫 beforeEnter。路由独享守卫 beforeEnter 只有从一个路由切换到另一个不同路由时才会被触发,不会在 params、query 或 hash 改变时被触发。

例如,从 /users/2 进入 /users/3 或者从 /users/2#info 进入 /users/2#projects 时,都不会触发路由独享守卫。

现在有一个需求,在应用程序路由切换到编辑用户页时,编辑用户页需要确认上一个地址是否是查看用户详情页。如果地址不是查看用户详情页,则可能是用户在地址栏中复制/粘贴的地址,此时直接返回项目的首页,不允许用户操作编辑用户页。

这个需求完全可以利用路由独享守卫来实现,只需要在查看用户详情页的路由配置对象中添加一个 name 名称。假如将 name 名称设置为 userDetail,并且在编辑用户页的路由对象中添加路由独享守卫内容,利用来源路由对象的名称和 userDetail 是否匹配来确认是进行首页的定位,还是继续访问操作。

添加的代码如下:
{
  name: 'userEdit',
  ...
  ...
  beforeEnter: (to, from, next) => {
    if (from.name !== 'userDetail') {
      next('/');
    } else {
      next();
    }
  },
},
如果用户是按照正常操作流程从查看用户详情页点击“编辑用户”按钮进入编辑用户页的,项目就没有任何问题,可以直接进入编辑用户页,但如果用户在首页或者其他页面中,将用户编辑的地址直接粘贴在地址栏中,那么项目会直接返回首页。

Vue路由组件内守卫确认是否重复点击相同内容及确认是否离开页面

路由守卫中的路由组件内守卫可以实现一些提示与判断的功能。比如在查看用户详情时点击用户列表中的列表项,如果已经选中一个用户,那么这个用户允许被再次点击吗?其实我们可以在查看用户详情页中执行判断操作。

从 vue-router 模块中引入 onBeforeRouteUpdate 路由组件的更新守卫钩子函数,并且利用该函数进行来源和目标路由对象参数的条件判断。只有在参数不一致,也就是点击了不同的列表项时,应用才会做出弹窗提示,否则不做任何操作。

不同的列表项时,应用才会做出弹窗提示,否则不做任何操作。给 views/user/UserDetail.vue 文件添加如下代码:
<script setup>
// 引入路由组件的更新守卫钩子函数 onBeforeRouteUpdate
import { onBeforeRouteUpdate } from 'vue-router';

...
...

onBeforeRouteUpdate((to, from) => {
  if (to.params.id !== from.params.id) {
    alert(`已经切换查看不同的用户信息,目标用户id为${to.params.id}`);
  }
});
</script>
除了路由组件的更新守卫钩子函数,还可以使用路由组件的离开守卫钩子函数 onBeforeRouteLeave 进行指定页面离开前的确认操作。

比如在编辑用户页中,从 vue-router 模块中引入该钩子函数后,调用该钩子函数,并且利用 window.confirm 进行确认对话框的确认处理。只有在点击“确定”按钮(也就是返回值为 true)后,才会离开编辑用户页,否则将会停留在编辑用户页无法离开。

给 views/user/UserEdit.vue 文件添加如下代码:
<script setup>
import { onBeforeRouteLeave } from 'vue-router';

...
...

// 组件离开前守卫
onBeforeRouteLeave((to, from, next) => {
  // 只有点击“确定”按钮后,才离开当前页面
  if (window.confirm('你是否确认离开本页面?点击“取消”按钮将停留于此页面!')) {
    next();
  }
});
</script>

相关文章