阐述 Vue Router 源码中导航守卫 (Navigation Guards) 的执行流程,包括 `beforeEach`, `beforeResolve`, `afterEach` 的顺序和作用。

好的,各位观众老爷们,今天咱们来聊聊 Vue Router 里的导航守卫,这玩意儿就像咱们进出皇宫的层层把关,保证安全又可控。别看名字挺唬人,其实理解了它的执行流程,你就能像指挥千军万马一样掌控路由的跳转。

开场白:导航守卫是啥?

导航守卫,简单来说,就是在路由跳转过程中,给你提供的一系列钩子函数。你可以通过这些钩子函数,在路由跳转的不同阶段,做一些事情,比如:

  • 权限校验: 判断用户是否有权限访问某个页面。
  • 页面统计: 记录用户的访问轨迹。
  • 数据预取: 在页面渲染前,先把数据准备好。
  • 滚动条位置还原: 记住上次浏览的位置,下次进来时自动滚动到那里。
  • 等等等等…

这就像你进饭店,服务员会在门口问你几位,引导你去哪里,点什么菜,吃完结账送你出门一样。导航守卫就是 Vue Router 的服务员,帮你处理路由跳转的各种杂事。

正文:导航守卫的种类和执行顺序

Vue Router 提供了三种全局导航守卫,分别是:

  1. beforeEach (全局前置守卫): 在每次路由跳转都会执行。这是你进入饭店大门前的保安,先检查你有没有带刀,穿着是否得体。
  2. beforeResolve (全局解析守卫): 在所有组件内的守卫和异步路由组件解析之后,导航被确认之前调用。可以理解为饭店领班,在你入座前,确认一下你的订单是否正确。
  3. afterEach (全局后置钩子): 在每次路由跳转都会执行。这是你吃完饭出门时,服务员跟你说声谢谢惠顾。

除了全局导航守卫,还有组件内的守卫和路由独享的守卫。咱们今天重点讲全局的,因为他们最常用,也最能体现导航守卫的核心思想。

执行顺序:beforeEach -> beforeResolve -> afterEach

这个顺序很重要,就像你先安检,再点菜,最后出门一样,不能乱来。

详细剖析:每个守卫的作用和用法

1. beforeEach (全局前置守卫)

这是最重要的一个守卫,也是最常用的。它会在每次路由跳转前执行,你可以用它来做很多事情,比如权限校验。

  • 参数:

    • to: 即将要进入的目标 路由对象。
    • from: 当前导航正要离开的路由对象。
    • next: 一个函数,用来控制路由的跳转。
  • next 函数:

    next 函数是 beforeEach 守卫的核心,它决定了路由是否能够继续跳转。你可以把它想象成一把钥匙,只有拿到这把钥匙,才能打开通往目标页面的大门。

    • next(): 允许路由继续跳转到目标页面。
    • next(false): 中断当前的导航。
    • next('/')next({ path: '/' }): 跳转到不同的路由。
    • next(error): 中断导航,并将错误传递给 router.onError() 注册过的回调。
  • 代码示例:

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    Vue.use(VueRouter)
    
    const routes = [
      {
        path: '/',
        component: { template: '<div>Home Page</div>' }
      },
      {
        path: '/profile',
        component: { template: '<div>Profile Page</div>' },
        meta: { requiresAuth: true } // 需要登录才能访问
      },
      {
        path: '/login',
        component: { template: '<div>Login Page</div>' }
      }
    ]
    
    const router = new VueRouter({
      routes
    })
    
    router.beforeEach((to, from, next) => {
      // 假设有一个 isAuthenticated 函数,用来判断用户是否已登录
      const isAuthenticated = () => {
        // 这里只是一个模拟,实际情况需要根据你的登录逻辑来判断
        return localStorage.getItem('token') !== null;
      };
    
      if (to.meta.requiresAuth) {
        // 如果目标路由需要登录才能访问
        if (isAuthenticated()) {
          // 如果用户已登录,则允许继续跳转
          next();
        } else {
          // 如果用户未登录,则跳转到登录页面
          next('/login');
        }
      } else {
        // 如果目标路由不需要登录,则允许继续跳转
        next();
      }
    })
    
    export default router

    在这个例子中,我们定义了一个 requiresAuthmeta 字段,用来标记哪些页面需要登录才能访问。在 beforeEach 守卫中,我们判断目标路由是否需要登录,如果需要,则判断用户是否已登录,如果已登录,则允许继续跳转,否则跳转到登录页面。

    这个例子就像饭店规定,VIP包间需要会员卡才能进入,保安会在门口检查你的会员卡。

2. beforeResolve (全局解析守卫)

这个守卫用的比较少,但也有它的用处。它会在所有组件内的守卫和异步路由组件解析之后,导航被确认之前调用。

  • 作用:

    beforeResolve 守卫主要用于在路由被确认之前,执行一些异步操作,比如获取一些数据,或者做一些其他的准备工作。

  • beforeEach 的区别:

    beforeResolve 守卫会在所有组件内的守卫和异步路由组件解析之后执行,这意味着它可以访问到组件实例和异步路由组件。而 beforeEach 守卫在这些组件解析之前执行,无法访问到这些信息。

  • 代码示例:

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    Vue.use(VueRouter)
    
    const routes = [
      {
        path: '/user/:id',
        component: () => import('./components/User.vue') // 异步组件
      }
    ]
    
    const router = new VueRouter({
      routes
    })
    
    router.beforeResolve((to, from, next) => {
      // 在异步组件解析之后,获取用户信息
      import('./api/user')
        .then(userApi => {
          return userApi.getUser(to.params.id);
        })
        .then(user => {
          // 将用户信息存储到路由对象中,方便组件使用
          to.meta.user = user;
          next();
        })
        .catch(error => {
          console.error('Failed to fetch user data:', error);
          next(error); // 将错误传递给 router.onError()
        });
    });
    
    export default router

    在这个例子中,我们在 beforeResolve 守卫中,异步加载用户信息,并将用户信息存储到路由对象的 meta 字段中。这样,在组件中,就可以通过 this.$route.meta.user 来访问用户信息了。

    这个例子就像饭店领班,在客人入座前,确认一下客人的预定信息,然后把客人的喜好告诉服务员。

3. afterEach (全局后置钩子)

这个钩子函数会在每次路由跳转后执行,它不会接收 next 函数,因为路由已经跳转完成了,你只能做一些收尾工作。

  • 作用:

    afterEach 钩子函数主要用于做一些页面统计、日志记录等收尾工作。

  • 参数:

    • to: 即将要进入的目标 路由对象。
    • from: 当前导航正要离开的路由对象。
  • 代码示例:

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    Vue.use(VueRouter)
    
    const routes = [
      {
        path: '/',
        component: { template: '<div>Home Page</div>' }
      },
      {
        path: '/profile',
        component: { template: '<div>Profile Page</div>' }
      }
    ]
    
    const router = new VueRouter({
      routes
    })
    
    router.afterEach((to, from) => {
      // 记录页面访问日志
      console.log(`Navigated from ${from.path} to ${to.path}`);
    
      // 可以使用第三方库进行页面统计,比如 Google Analytics
      // ga('set', 'page', to.path);
      // ga('send', 'pageview');
    })
    
    export default router

    在这个例子中,我们在 afterEach 钩子函数中,记录了页面的访问日志,并使用了 Google Analytics 进行页面统计。

    这个例子就像饭店服务员,在你出门时,跟你说声谢谢惠顾,并记录你的消费信息。

总结:导航守卫的执行流程

下面用一个表格来总结一下导航守卫的执行流程:

阶段 守卫类型 作用 参数 next 函数
路由跳转前 beforeEach 在每次路由跳转前执行,可以进行权限校验、数据预取等操作。 to, from, next 可用
路由解析后 beforeResolve 在所有组件内的守卫和异步路由组件解析之后,导航被确认之前调用。 to, from, next 可用
路由跳转后 afterEach 在每次路由跳转后执行,可以进行页面统计、日志记录等操作。 to, from 不可用

代码示例:一个完整的例子

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    component: { template: '<div>Home Page</div>' }
  },
  {
    path: '/profile',
    component: { template: '<div>Profile Page</div>' },
    meta: { requiresAuth: true }
  },
  {
    path: '/login',
    component: { template: '<div>Login Page</div>' }
  },
  {
    path: '/user/:id',
    component: () => import('./components/User.vue') // 异步组件
  }
]

const router = new VueRouter({
  routes
})

router.beforeEach((to, from, next) => {
  const isAuthenticated = () => {
    return localStorage.getItem('token') !== null;
  };

  if (to.meta.requiresAuth) {
    if (isAuthenticated()) {
      next();
    } else {
      next('/login');
    }
  } else {
    next();
  }
})

router.beforeResolve((to, from, next) => {
  if (to.path.startsWith('/user/')) {
    import('./api/user')
      .then(userApi => {
        return userApi.getUser(to.params.id);
      })
      .then(user => {
        to.meta.user = user;
        next();
      })
      .catch(error => {
        console.error('Failed to fetch user data:', error);
        next(error);
      });
  } else {
    next();
  }
});

router.afterEach((to, from) => {
  console.log(`Navigated from ${from.path} to ${to.path}`);
})

export default router

注意事项:

  • 异步操作:beforeEachbeforeResolve 守卫中,可以执行异步操作,但一定要确保 next 函数在异步操作完成后调用,否则路由会一直卡在那里。
  • 错误处理: 如果在守卫中发生了错误,可以使用 next(error) 将错误传递给 router.onError() 注册过的回调。
  • 避免死循环: 在守卫中,要避免出现死循环,比如在 beforeEach 守卫中,总是跳转到同一个页面,导致守卫被无限循环调用。
  • 组件内的守卫: 除了全局守卫,Vue Router 还提供了组件内的守卫,比如 beforeRouteEnter, beforeRouteUpdate, beforeRouteLeave。这些守卫只能在组件内部使用,可以用来做一些组件相关的操作。
  • 路由独享的守卫: 可以在路由配置中直接定义 beforeEnter 守卫,它只对当前路由有效。

总结:

导航守卫是 Vue Router 中非常重要的一个特性,它可以让你在路由跳转的不同阶段,做一些事情,比如权限校验、数据预取、页面统计等。理解了导航守卫的执行流程,你就能更好地控制路由的跳转,提升用户体验。

好了,今天的讲座就到这里。希望大家能够掌握导航守卫的用法,在实际项目中灵活运用,写出更加健壮的 Vue 应用。 散会!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注