Code前端首页关于Code前端联系我们

Vue Router生命周期包含哪些核心环节?

terry 1天前 阅读数 22 #Vue

p不少刚接触Vue Router的同学,总会疑惑它的生命周期咋运作,和Vue组件自身生命周期咋配合,开发时又该咋利用这些阶段做权限控制、数据加载这些事儿,今天就借着问答的形式,把Vue Router生命周期的关键逻辑、实际用法掰碎了聊聊。
要理解Vue Router的生命周期,得先明白“导航过程”的全貌,从用户点击链接或调用router.push开始,到新页面渲染完成,整个流程里嵌套着不同层级的“守卫”(钩子函数)和Vue组件自身的生命周期钩子。

核心环节可以分成这几类:

  • 全局守卫:作用于整个路由系统,比如router.beforeEach(导航触发后立即执行)、router.beforeResolve(组件解析后、导航确认前)、router.afterEach(导航完成后)。
  • 路由独享守卫:只针对某一条路由规则,用beforeEnter定义在路由配置里。
  • 组件内守卫:组件自己身上的路由钩子,像beforeRouteEnter(进入路由前,组件实例还没创建)、beforeRouteUpdate(路由参数变化但组件复用)、beforeRouteLeave(离开路由前)。
  • Vue组件自身生命周期:比如createdmountedbeforeDestroy这些,路由切换时组件的创建、销毁会触发它们。

举个直观的流程例子:用户从首页跳转到文章详情页,流程是这样的:

  1. 调用router.push('/article'),导航开始;
  2. 触发全局beforeEach(可以在这里判断权限,比如是否登录);
  3. 匹配到/article的路由规则,触发该路由的独享beforeEnter(如果有的话,比如检查文章是否公开);
  4. 解析目标组件(如果是异步组件,这时候才会加载);
  5. 触发目标组件的beforeRouteEnter(此时组件实例this还没生成);
  6. 触发全局beforeResolve(确保所有守卫都执行完,避免遗漏);
  7. 确认导航,销毁旧组件(如果有的话),触发旧组件的beforeDestroy等;
  8. 创建新组件实例,触发新组件的createdmounted
  9. 触发全局afterEach(可以在这里改页面标题、埋点统计)。

全局守卫在生命周期里起啥作用?咋用?

全局守卫是“管全局”的,所有路由切换都会经过它们,适合做全局权限控制、埋点、页面跳转拦截这些通用逻辑。

  • router.beforeEach:导航刚触发就执行,是“第一道关卡”。
    场景举例:用户访问需要登录的页面时,判断store里的登录状态,没登录就跳转到登录页,代码大概长这样:

    router.beforeEach((to, from, next) => {
      if (to.meta.requiresAuth && !isLogin()) {
        next('/login'); // 没登录,跳登录页
      } else {
        next(); // 放行
      }
    });

    注意next必须调用,否则导航会卡住;而且别重复调用next,比如在ifelse里都写会报错。

  • router.beforeResolve:它在“组件内守卫”和“异步路由组件加载完成后”执行,比beforeEach晚一步。
    作用是确保所有异步操作完成后再确认导航,避免“组件还没加载完就跳转”的问题,比如有个路由是异步加载的(component: () => import('./Article.vue')),beforeResolve会等这个组件加载完再执行后续逻辑。

  • router.afterEach:导航完成后执行,此时页面已经渲染好了。
    适合做不影响导航的收尾工作,比如修改页面标题(document.title = to.meta.title)、统计页面访问次数、重置滚动条位置(window.scrollTo(0, 0)),因为它不影响导航流程,所以没有next参数。

路由独享守卫和全局守卫有啥区别?啥场景用?

路由独享守卫叫beforeEnter,是写在单个路由配置对象里的,只有匹配到这个路由时才会触发,作用范围更“精准”。

区别很明显:全局守卫管所有路由,beforeEnter只管自己,场景上,适合单一路由的特殊逻辑,全局守卫里写太冗余的情况。

举个例子:后台系统里,“订单编辑页”需要额外验证用户是否有编辑权限(即使已经登录,还要检查角色),这时候给/order/edit这条路由加beforeEnter

const routes = [
  {
    path: '/order/edit',
    component: OrderEdit,
    beforeEnter: (to, from, next) => {
      if (hasEditPermission()) {
        next();
      } else {
        next('/order/list'); // 没权限跳订单列表
      }
    }
  }
];

如果把这段逻辑放到全局beforeEach里,得判断to.path是不是/order/edit,代码会变繁琐;用beforeEnter就只影响这一个路由,清爽很多。

组件内的路由钩子咋和组件自身生命周期配合?

组件内的三个路由钩子(beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave),和createdmounted这些Vue自身生命周期钩子是“穿插执行”的,理解它们的顺序和作用,才能在组件里精准控制逻辑。

  • beforeRouteEnter:进入路由前触发,组件实例还没创建(所以thisundefined)。
    啥时候用?适合进入页面前预加载数据,因为组件还没创建,要拿数据给组件用,得用next的回调:

    export default {
      beforeRouteEnter(to, from, next) {
        axios.get('/api/article/' + to.params.id).then(res => {
          next(vm => { // vm是组件实例,相当于this
            vm.article = res.data;
          });
        });
      },
      created() {
        // 这里也能请求数据,但beforeRouteEnter里请求能让数据在组件渲染前准备好,避免页面闪烁
      }
    };

    执行顺序:beforeRouteEntercreatedmounted

  • beforeRouteUpdate:路由参数变化,但组件复用的时候触发(比如从/user/1跳到/user/2,用户组件复用)。
    场景:根据新参数更新组件数据,比如用户ID变了,要重新拉取用户信息:

    beforeRouteUpdate(to, from, next) {
      this.fetchUser(to.params.id); // 直接用this,因为组件已经创建了
      next();
    }

    执行顺序:beforeRouteUpdateupdated(如果有数据更新触发updated的话)。

  • beforeRouteLeave:离开当前路由前触发,可以阻止导航(比如表单没保存)。
    例子:用户填了表单没提交,离开时弹窗确认:

    beforeRouteLeave(to, from, next) {
      if (this.formDirty) {
        if (window.confirm('表单没保存,确定离开?')) {
          next(); // 确认就走
        } else {
          next(false); // 取消就留在当前页
        }
      } else {
        next(); // 表单干净,直接走
      }
    }

    执行顺序:离开时,先触发beforeRouteLeave,再触发旧组件的beforeDestroy

实际开发中,咋结合路由生命周期做权限管理?

权限管理是路由生命周期最常用的场景之一,要分全局权限、路由级权限、组件内权限三层来搞,互相配合。

  1. 全局权限(全局守卫)
    router.beforeEach拦截所有路由,判断用户是否登录、是否有基础权限,比如后台系统,除了登录页,其他页都要登录:

    router.beforeEach((to, from, next) => {
      const isLogin = localStorage.getItem('token');
      if (to.path !== '/login' && !isLogin) {
        next('/login');
      } else {
        next();
      }
    });
  2. 路由级权限(路由独享守卫)
    某些路由需要更细的权限,管理员面板”只有管理员角色能进,给对应的路由加beforeEnter

    {
      path: '/admin',
      component: AdminPanel,
      beforeEnter: (to, from, next) => {
        const userRole = localStorage.getItem('role');
        if (userRole === 'admin') {
          next();
        } else {
          next('/403'); // 没权限跳403页面
        }
      }
    }
  3. 组件内权限(组件内守卫)
    组件渲染后,某些按钮或模块可能需要权限控制,比如文章编辑页,只有作者能编辑,在beforeRouteEnter里判断:

    beforeRouteEnter(to, from, next) {
      const article = getArticle(to.params.id);
      if (article.authorId === currentUserId()) {
        next(); // 是作者,放行
      } else {
        next('/article/' + to.params.id); // 不是作者,跳文章详情页
      }
    }

三层配合起来,既能拦截未登录用户,又能限制不同角色的路由访问,还能在组件层面做细粒度控制。

路由生命周期里的数据预加载咋做?

数据预加载能让页面切换更流畅,避免“先渲染再请求数据导致的空白/闪烁”,关键是在导航完成前把数据准备好,这时候路由守卫就是天然的“预加载时机”。

常见做法有两种:

  • 在全局/路由独享守卫里请求数据
    比如文章列表页,在router.beforeEach或路由的beforeEnter里请求数据,然后把数据存到Vuex或传递给组件,但要注意,这种方式数据是全局的,多个组件共享时要考虑状态管理。

  • 在组件内的beforeRouteEnter里请求数据
    前面举过例子,用next(vm => {})把请求到的数据传给组件实例,这种方式数据是组件级的,更灵活。

举个完整例子:用户进入“商品详情页”,需要先拿到商品ID,请求详情数据,再渲染页面。

// 路由配置
{
  path: '/product/:id',
  component: ProductDetail,
  // 也可以在beforeEnter里请求数据,存到Vuex
  beforeEnter: (to, from, next) => {
    fetchProduct(to.params.id).then(res => {
      store.commit('SET_PRODUCT', res.data);
      next();
    });
  }
}
// ProductDetail组件内
export default {
  data() {
    return {
      product: {}
    };
  },
  beforeRouteEnter(to, from, next) {
    // 也可以在这里请求,适合组件私有的数据
    fetchProduct(to.params.id).then(res => {
      next(vm => {
        vm.product = res.data;
      });
    });
  },
  mounted() {
    // 如果守卫里没请求,这里再请求就会有延迟,页面先渲染再填充数据
  }
};

两种方式各有优劣:全局/路由守卫里请求,数据能提前存到Vuex,多个组件能复用;组件内守卫请求,数据是组件自己的,更解耦,实际开发中可以根据数据的复用性选择。

路由切换时,组件销毁和重建的逻辑咋影响生命周期?

路由切换时,组件是否销毁重建,取决于路由对应的组件是否相同,这会直接影响Vue组件自身生命周期和路由钩子的执行顺序。

  • 情况1:路由切换导致组件不同(比如从/home跳到/about):
    旧组件会经历:beforeRouteLeavebeforeDestroydestroyed
    新组件会经历:beforeRouteEntercreatedmounted

  • 情况2:路由切换但组件复用(比如从/user/1跳到/user/2,用户组件复用):
    旧组件触发beforeRouteLeave
    新组件触发beforeRouteUpdate(代替beforeRouteEnter,因为组件没销毁);
    然后触发updated(如果有数据更新)。

理解这个逻辑,能帮我们优化性能和避免Bug,比如组件复用场景下,数据不会自动更新,得在beforeRouteUpdate里手动请求新数据,否则页面还显示旧用户的信息。

路由生命周期里有哪些容易踩的坑?

最后聊聊常见“踩坑点”,避开这些能少掉头发:

  1. beforeRouteEnter里拿不到this
    因为组件实例还没创建,所以不能直接this.xxx,必须通过next(vm => { vm.xxx = ... })来访问组件实例。

  2. 全局守卫里next的错误使用
    比如在beforeEach里忘记调用next(),导航会一直pending;或者多次调用next(比如ifelse里都调用),会导致错误。

  3. 组件复用导致数据没更新
    路由参数变化但组件复用(如/user/:id)时,createdmounted不会重新执行,所以要在beforeRouteUpdate里处理数据更新,否则页面显示旧数据。

  4. 异步路由组件的加载时机
    component: () => import('./MyComponent.vue')这种异步加载时,组件加载是异步的,beforeResolve会等加载完成后再执行,所以要注意导航等待时间,避免白屏太久。

Vue Router的生命周期是“导航流程 + 守卫 + 组件生命周期”的结合体,理解每个环节的触发时机和作用,才能在权限控制、数据加载、页面跳转拦截这些场景里游刃有余,实际开发中多结合业务场景调试,把各个钩子的执行顺序摸透,很多问题自然就解决啦~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门