主题
导航守卫
在 uni-simple-router
中,导航守卫(Navigation Guards
)是一组用于控制路由导航的钩子函数。它们可以在路由切换前、切换后以及切换过程中执行一些逻辑操作,用于实现权限控制、全局拦截、页面跳转等功能。
守卫参数标准
以下是 uni-simple-router
中常用的导航守卫,它们都符合以下参数标准及返回值:
- 全局前置守卫
- 全局解析守卫
- 全局后置钩子
- 路由独享的守卫
- 组件内的守卫
每个守卫方法接收两个参数:
to
: 即将要进入的目标from
: 当前导航正要离开的路由
守卫方法可以返回以下值:
false
: 取消当前的导航。如果浏览器的 URL 改变了(例如用户手动操作或浏览器后退按钮),URL 地址会重置为上一个路由地址(即 from 路由对应的地址)。如果是非H5端(如小程序等),导航及页面不会发生任何变化。新路由地址(字符串或路由对象): 通过一个路由地址跳转到一个不同的地址,就像调用 router.push() 方法一样。你可以设置一些配置项,例如
navType: 'pushTab'
或name: 'home'
。当前的导航会被中断,并开始一个新的导航,效果就和 from 路由一样。undefined
或true
: 表示导航是有效的,并且继续调用下一个导航守卫。
在 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
守卫,你可以为特定的路由配置独立的导航逻辑,以满足不同路由的需求,了解详细请参考守卫参数标准。
组件内的守卫
最后,你可以在路由组件内直接定义路由导航守卫(传递给路由配置的)
beforeRouteEnter
: 在进入路由前被调用。这个守卫在组件实例被创建之前被调用,因此在这个守卫内部无法访问组件实例。beforeRouteUpdate
: 在当前路由改变,但是组件被复用时被调用。例如,从/user/1
切换到/user/2
,同一个组件实例被复用,但是路由参数发生了变化。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 函数 来编写组件,你可以通过 onBeforeRouteUpdate
和 onBeforeRouteLeave
分别添加 update
和 leave
守卫。 请参考组合 API 部分以获得更多细节。
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 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 全局前置守卫中,我们根据不同的条件进行权限验证。首先,如果路由要求登录验证且用户未登录,我们将用户重定向到登录页面。其次,如果路由要求特定角色且用户角色不符合要求,我们将用户重定向到未授权页面。最后,如果路由要求特定权限且用户权限不足,同样将用户重定向到未授权页面。只有在通过所有权限验证后,才会继续导航到目标路由。
通过以上示例,你可以根据用户的身份信息、权限列表和路由的权限要求,进行更复杂的权限验证逻辑,确保只有具备相应身份、角色和权限的用户才能访问相应的路由。你可以根据实际情况进行适当的调整和扩展。
陷阱提示
- 请注意,以上示例仅供参考,你需要根据自己的项目需求进行适当的调整和修改。实际项目中的权限验证逻辑可能更加复杂,涉及到后端接口的调用、角色管理、权限控制等。