Skip to content

导航守卫

uni-simple-router 中,导航守卫(Navigation Guards)是一组用于控制路由导航的钩子函数。它们可以在路由切换前、切换后以及切换过程中执行一些逻辑操作,用于实现权限控制、全局拦截、页面跳转等功能。

守卫参数标准

以下是 uni-simple-router 中常用的导航守卫,它们都符合以下参数标准及返回值:

每个守卫方法接收两个参数:

  • to: 即将要进入的目标
  • from: 当前导航正要离开的路由

守卫方法可以返回以下值:

  • false: 取消当前的导航。如果浏览器的 URL 改变了(例如用户手动操作或浏览器后退按钮),URL 地址会重置为上一个路由地址(即 from 路由对应的地址)。如果是非H5端(如小程序等),导航及页面不会发生任何变化。

  • 新路由地址(字符串或路由对象): 通过一个路由地址跳转到一个不同的地址,就像调用 router.push() 方法一样。你可以设置一些配置项,例如 navType: 'pushTab'name: 'home'。当前的导航会被中断,并开始一个新的导航,效果就和 from 路由一样。

  • undefinedtrue: 表示导航是有效的,并且继续调用下一个导航守卫。

uni-simple-router 中,所有守卫会按照创建顺序调用,并且守卫是异步解析执行的。当一个导航被触发时,在所有守卫完成解析之前,导航会一直处于等待状态。

陷阱提示

  • 除了 afterEach 守卫之外的其他导航守卫都可以通过返回不同的值来控制导航行为。

全局前置守卫

通过使用 router.beforeEach 方法,你可以注册一个全局前置守卫,它会在每次路由切换之前被调用。这使得你能够执行一些全局的逻辑操作,以确保在路由切换前满足特定条件或进行必要的处理。这种方式常用于权限验证,路由拦截或参数过滤等功能的实现。

在每次路由切换之前,beforeEach 守卫会按照创建的顺序被调用。

下面是一个示例,展示如何使用 beforeEach 全局前置守卫:

ts
const router = createRouter({ ... })

router.beforeEach((to, from) => {
  // 执行一些全局逻辑操作
  console.log('Before navigation');

  // 进行权限验证或其他操作
  if (!checkUserAuthenticated()) {
    // 用户未登录,重定向到登录页或其他路由
    return { path: '/login' }
  }
  // 用户已经登录,继续导航
});

请注意,beforeEach 守卫可以通过返回不同的值来控制导航行为,例如取消导航、重定向到其他路由或继续导航到下一个守卫,了解详细请参考守卫参数标准

全局解析守卫

你也可以使用 router.beforeResolve 注册一个全局守卫。它与 router.beforeEach 类似,会在每次导航时触发。不同之处在于,解析守卫会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。

下面是一个示例,用于确保用户可以访问具有自定义元属性 requiresCamera 的路由:

ts
router.beforeResolve(async to => {
  if (to.meta.requiresCamera) {
    try {
      await askForCameraPermission()
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... 处理错误,然后取消导航
        return false
      } else {
        // 意料之外的错误,取消导航并把错误传给全局处理器
        throw error
      }
    }
  }
})

在上述示例中,我们使用 router.beforeResolve 注册了一个全局解析守卫。守卫函数会检查目标路由的 meta.requiresCamera 属性,如果为 true,则进行相机权限的检查。这里假设 askForCameraPermission() 是一个异步操作,返回一个 Promise,表示是否有相机权限。如果有权限,导航会继续进行,否则可以取消导航或重定向到其他路由,了解详细请参考守卫参数标准

router.beforeResolve 是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。

全局后置钩子

你还可以使用全局后置钩子 afterEach 注册一个在每次导航完成之后被调用的守卫函数。

afterEach 守卫与其他导航守卫不同,它不会接收任何参数,并且无法控制导航本身或改变导航的行为。它仅用于在导航完成后执行一些操作,例如页面统计、日志记录等。

下面是一个示例,展示如何使用 afterEach 全局后置钩子:

ts
router.afterEach((to, from) => {
  // 导航完成后执行的操作
  console.log('Navigation completed');
  trackPageView();
})

通过使用 afterEach 全局后置钩子,你可以在每次导航完成后执行一些通用的操作。这可以帮助你进行页面分析、日志记录或其他一些需要在导航之后触发的任务。请注意,afterEach 守卫无法控制导航行为,它只是在导航完成后提供一个钩子函数供你执行操作。

路由独享的守卫

使用路由独享的守卫 beforeEnter 来为特定的路由配置单独的导航守卫。

beforeEnter 守卫会在进入特定路由之前被调用,它可以用于在进入路由之前执行一些逻辑操作或进行权限验证。与全局前置守卫 beforeEach 不同,beforeEnter 守卫只会应用于当前路由。

下面是一个示例,展示如何使用 beforeEnter 守卫:

ts
const routes = [
  {
    path: '/public',
    name: 'public',
    beforeEnter: (to, from) => {
      // 在进入 '/public' 路由之前执行的操作
      console.log('Before entering public route');

      // 继续导航
    },
    component: Public
  },
  {
    path: '/private',
    name: 'private',
    beforeEnter: (to, from) => {
      // 在进入 '/private' 路由之前执行的操作
      console.log('Before entering private route');

      // 进行权限验证
      if (!checkUserAuthenticated()) {
        // 用户未登录,重定向到登录页
        return { path: '/login' }
      }

      // 继续导航
    },
    component: Private
  }
];

通过使用 beforeEnter 守卫,你可以为特定的路由配置独立的导航逻辑,以满足不同路由的需求,了解详细请参考守卫参数标准

组件内的守卫

最后,你可以在路由组件内直接定义路由导航守卫(传递给路由配置的)

  1. beforeRouteEnter: 在进入路由前被调用。这个守卫在组件实例被创建之前被调用,因此在这个守卫内部无法访问组件实例。

  2. beforeRouteUpdate: 在当前路由改变,但是组件被复用时被调用。例如,从 /user/1 切换到 /user/2,同一个组件实例被复用,但是路由参数发生了变化。

  3. beforeRouteLeave: 在离开当前路由时被调用。可以用来询问用户是否保存未提交的表单数据,或者执行一些离开前的清理操作。

下面是一个示例,展示如何在组件内使用这些守卫:

js
const UserDetails = {
  template: `...`,
  beforeRouteEnter(to, from) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this` !
    // 因为当守卫执行时,组件实例还没被创建!
  },
  beforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
    // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
  },
}

通过使用组件内的守卫,你可以对特定组件的导航进行个性化的控制和处理。这种方式常用于组件级别的权限验证、页面切换逻辑等场景,了解详细请参考守卫参数标准

使用组合 API

如果你正在使用 组合 API 和 setup 函数 来编写组件,你可以通过 onBeforeRouteUpdateonBeforeRouteLeave 分别添加 updateleave 守卫。 请参考组合 API 部分以获得更多细节。

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新,或者页面跳转。

守卫常用示例

beforeEach 守卫实现登录和身份权限验证:

当使用 uni-simple-router 中的 beforeEach 全局前置守卫进行复杂的权限验证时,你可以根据不同的权限需求和用户身份进行细致的控制。以下是一个示例:

js
import router from './router';

router.beforeEach((to, from) => {
  // 获取用户身份信息和权限列表
  const user = getUser();
  const permissions = getUserPermissions();

  // 判断用户是否登录
  const isAuthenticated = user !== null;

  if (to.meta.requiresAuth && !isAuthenticated) {
    // 路由需要登录验证,但用户未登录
    return { path: '/login' }
  } else if (to.meta.requiredRoles && !hasRequiredRoles(user, to.meta.requiredRoles)) {
    // 路由需要特定角色,但用户角色不符合要求
    return { path: '/unauthorized' };
  } else if (to.meta.requiredPermissions && !hasRequiredPermissions(permissions, to.meta.requiredPermissions)) {
    // 路由需要特定权限,但用户权限不足
    return { path: '/unauthorized' };
  } 
  // 路由通过权限验证
});

function getUser() {
  // 获取用户身份信息的逻辑
  // 可以根据实际情况获取用户的身份信息,例如从后端获取、从本地存储读取等
  // 示例中使用一个假设的 user 变量来表示用户身份
  const user = {
    id: 1,
    name: 'John Doe',
    role: 'admin',
  };
  return user;
}

function getUserPermissions() {
  // 获取用户权限列表的逻辑
  // 可以根据实际情况获取用户的权限列表,例如从后端获取、从本地存储读取等
  // 示例中使用一个假设的 permissions 变量来表示用户权限列表
  const permissions = ['read', 'write', 'delete'];
  return permissions;
}

function hasRequiredRoles(user, requiredRoles) {
  // 判断用户是否具备所需角色的逻辑
  return requiredRoles.some(role => role === user.role);
}

function hasRequiredPermissions(permissions, requiredPermissions) {
  // 判断用户是否具备所需权限的逻辑
  return requiredPermissions.every(permission => permissions.includes(permission));
}

在上述示例中,我们模拟了一个复杂的权限验证过程。首先,我们使用 getUser 函数获取用户的身份信息,例如用户ID、姓名和角色等。然后,我们使用 getUserPermissions 函数获取用户的权限列表,例如读取、写入和删除等权限。

接下来,在 beforeEach 全局前置守卫中,我们根据不同的条件进行权限验证。首先,如果路由要求登录验证且用户未登录,我们将用户重定向到登录页面。其次,如果路由要求特定角色且用户角色不符合要求,我们将用户重定向到未授权页面。最后,如果路由要求特定权限且用户权限不足,同样将用户重定向到未授权页面。只有在通过所有权限验证后,才会继续导航到目标路由。

通过以上示例,你可以根据用户的身份信息、权限列表和路由的权限要求,进行更复杂的权限验证逻辑,确保只有具备相应身份、角色和权限的用户才能访问相应的路由。你可以根据实际情况进行适当的调整和扩展。

陷阱提示

  • 请注意,以上示例仅供参考,你需要根据自己的项目需求进行适当的调整和修改。实际项目中的权限验证逻辑可能更加复杂,涉及到后端接口的调用、角色管理、权限控制等。
导航守卫 has loaded