如何利用`Vue Router`的`beforeRouteUpdate`与`beforeRouteLeave`?

Vue Router 导航守卫:beforeRouteUpdatebeforeRouteLeave 深度解析

大家好!今天我们来深入探讨 Vue Router 中两个重要的导航守卫:beforeRouteUpdatebeforeRouteLeave。 它们是 Vue Router 中强大的工具,允许我们在路由更新和离开时进行精确的控制和处理。

导航守卫概览

首先,简单回顾一下 Vue Router 的导航守卫。导航守卫本质上是一些钩子函数,在路由发生变化时被触发。 Vue Router 提供了全局、路由独享和组件内的导航守卫,它们分别作用于不同的层级。

导航守卫类型 作用范围 说明
全局守卫 应用的全局路由变化 beforeEachbeforeResolveafterEach。 作用于所有路由,常用于全局权限控制、埋点等。
路由独享守卫 单个路由配置 beforeEnter。 作用于特定路由,用于路由级别的权限控制等。
组件内守卫 组件实例的路由变化 beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave。 作用于组件内部,可以访问组件实例,用于处理组件相关的逻辑,例如保存状态、确认离开等。

我们今天的重点是组件内的导航守卫 beforeRouteUpdatebeforeRouteLeave

beforeRouteUpdate: 在同一组件内路由改变时触发

beforeRouteUpdate 守卫在以下情况下被触发:

  • 当前路由对应的组件已经渲染(即组件已经存在于 DOM 中)。
  • 路由发生了变化,但变化后的路由仍然渲染同一个组件。 这通常发生在使用了动态路由参数,例如 /users/:id,从 /users/1 导航到 /users/2 的情况。

何时使用 beforeRouteUpdate?

  • 刷新组件数据: 当路由参数改变时,你可能需要重新从服务器获取数据,并更新组件的状态。
  • 响应路由参数变化: 根据新的路由参数,执行一些副作用操作,例如改变组件的显示状态、播放新的动画等。

示例代码:

<template>
  <div>
    <h1>User ID: {{ userId }}</h1>
    <p>User Name: {{ userName }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      userId: null,
      userName: '',
    };
  },
  watch: {
    userId(newVal, oldVal) {
      console.log(`User ID changed from ${oldVal} to ${newVal}`);
    }
  },
  async beforeRouteUpdate(to, from, next) {
    // to: 即将进入的目标路由对象
    // from: 当前导航正要离开的路由对象
    // next: 调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。

    this.userId = to.params.id;
    try {
      const user = await this.fetchUser(this.userId);
      this.userName = user.name;
      next(); // 继续导航
    } catch (error) {
      console.error('Failed to fetch user:', error);
      next(false); // 中断导航。  也可以使用 next('/') or next({ name: 'home' }) 重定向。
    }
  },
  async mounted() {
    //组件挂载后,初始化 userId 和 userName
    this.userId = this.$route.params.id;
    try {
      const user = await this.fetchUser(this.userId);
      this.userName = user.name;
    } catch (error) {
      console.error('Failed to fetch user:', error);
    }
  },
  methods: {
    async fetchUser(id) {
      // 模拟 API 调用
      return new Promise(resolve => {
        setTimeout(() => {
          resolve({ id: id, name: `User ${id}` });
        }, 500);
      });
    },
  },
};
</script>

代码解释:

  1. 数据初始化:data 中声明了 userIduserName,用于存储用户信息。
  2. beforeRouteUpdate 守卫:
    • 它接收三个参数:to (即将进入的路由对象), from (当前导航正要离开的路由对象) 和 next (用于控制导航流程的函数)。
    • 首先,从 to.params 中获取新的 userId
    • 然后,调用 fetchUser 方法模拟从服务器获取用户信息。
    • 如果成功获取到用户信息,则更新 userName,并调用 next() 继续导航。
    • 如果获取用户信息失败,则输出错误信息,并调用 next(false) 中断导航。你也可以使用 next('/')next({ name: 'home' }) 重定向到其他路由。
  3. mounted 钩子:
    • 在组件挂载后,初始化 userIduserName。 这是因为第一次加载组件时,beforeRouteUpdate 不会被触发(因为组件还未挂载)。 所以需要在 mounted 钩子中进行初始化。
  4. watch
    • 监听userId的变化,在控制台输出变化信息

注意事项:

  • 一定要调用 next()next(false)next(path) 来 resolve 这个钩子,否则导航会被阻塞。
  • 如果使用了 async/await,请确保 beforeRouteUpdate 函数声明为 async
  • 考虑错误处理,如果数据获取失败,应该适当地中断导航或重定向到错误页面。

beforeRouteLeave: 离开当前路由时触发

beforeRouteLeave 守卫在以下情况下被触发:

  • 组件即将被卸载,因为用户要导航到不同的路由

何时使用 beforeRouteLeave?

  • 确认用户离开: 如果用户在当前页面进行了未保存的更改,你可以使用 beforeRouteLeave 弹出一个确认对话框,询问用户是否要放弃更改。
  • 保存组件状态: 在用户离开页面之前,你可以保存组件的状态到本地存储或其他地方,以便下次用户返回时可以恢复状态。
  • 清理资源: 释放组件占用的资源,例如取消订阅的事件、停止运行的定时器等。

示例代码:

<template>
  <div>
    <h1>Edit Article</h1>
    <textarea v-model="articleContent"></textarea>
  </div>
</template>

<script>
export default {
  data() {
    return {
      articleContent: '',
      isContentChanged: false,
    };
  },
  watch: {
    articleContent(newValue, oldValue) {
      this.isContentChanged = newValue !== oldValue;
    },
  },
  beforeRouteLeave(to, from, next) {
    if (this.isContentChanged) {
      const confirmLeave = window.confirm(
        'You have unsaved changes. Are you sure you want to leave?'
      );
      if (confirmLeave) {
        next(); // 允许离开
      } else {
        next(false); // 中断离开
      }
    } else {
      next(); // 允许离开
    }
  },
};
</script>

代码解释:

  1. 数据声明:
    • articleContent 用于存储文章内容。
    • isContentChanged 用于标记文章内容是否被修改过。
  2. watch 监听器:
    • 监听 articleContent 的变化,如果内容发生变化,则将 isContentChanged 设置为 true
  3. beforeRouteLeave 守卫:
    • 首先检查 isContentChanged 的值。
    • 如果 isContentChangedtrue,则弹出一个确认对话框,询问用户是否要放弃更改。
    • 如果用户点击 "确定",则调用 next() 允许离开。
    • 如果用户点击 "取消",则调用 next(false) 中断离开。
    • 如果 isContentChangedfalse,则直接调用 next() 允许离开。

注意事项:

  • beforeRouteUpdate 类似,一定要调用 next()next(false)next(path) 来 resolve 这个钩子。
  • beforeRouteLeave 守卫不能访问组件实例 this,因为在 beforeRouteLeave 触发时,组件实例可能已经被销毁。 但是,在Vue 2.2.0 之后,你可以通过 this 访问组件实例。
  • 在弹出确认对话框时,要考虑用户体验,避免过度打扰用户。

路由参数变化与组件复用策略

Vue Router 默认情况下,当路由参数发生变化,并且新的路由对应于相同的组件类型时,会复用现有的组件实例,而不是销毁并重新创建它。 这就是 beforeRouteUpdate 存在的意义。

如果不想复用组件,可以使用 key attribute 强制 Vue 重新渲染组件:

<router-view :key="$route.fullPath"></router-view>

在这种情况下,每次路由变化时,$route.fullPath 都会改变,导致 Vue 认为这是一个不同的组件,从而重新渲染。 但通常,复用组件可以提高性能,所以应该优先考虑使用 beforeRouteUpdate 来处理路由参数变化。

导航守卫的执行顺序

了解导航守卫的执行顺序非常重要,可以帮助我们更好地理解路由的工作流程。 以下是完整的导航解析流程:

  1. 导航被触发。
  2. 离开的守卫被调用。
    • beforeRouteLeave (组件内守卫)
  3. 全局 beforeEach 守卫被调用。
  4. 重用组件的守卫被调用。
    • beforeRouteUpdate (组件内守卫)
  5. 进入的守卫被调用。
    • beforeEnter (路由独享守卫)
  6. 解析异步路由组件。
  7. 全局 beforeResolve 守卫被调用。
  8. 组件被激活。
    • beforeRouteEnter (组件内守卫)
  9. 导航被确认。
  10. 全局 afterEach 钩子被调用。
  11. DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

实际应用场景

  • 电商网站商品详情页: 当用户点击不同商品的链接时,路由参数 productId 会发生变化。 beforeRouteUpdate 可以用于根据新的 productId 重新加载商品信息。 beforeRouteLeave 可以用于提示用户是否保存对商品信息的修改。
  • 博客文章编辑页: beforeRouteLeave 可以用于在用户离开编辑页面之前,提示用户保存未保存的文章。
  • 表单页面: beforeRouteLeave 可以用于在用户离开表单页面之前,提示用户是否确认放弃填写的表单数据。
  • 视频播放页面: 当视频播放到一半,用户切换到其他视频,可以使用beforeRouteLeave保存当前播放进度,下次返回时可以从上次离开的地方继续播放。

最佳实践

  • 保持守卫函数的简洁性: 避免在守卫函数中执行过于复杂的操作,可以将复杂逻辑提取到单独的方法中。
  • 处理异步操作: 如果守卫函数中包含异步操作,请确保正确处理 Promise 的状态,并使用 async/await 简化代码。
  • 错误处理: 考虑各种可能的错误情况,并适当地处理错误,避免导致导航失败。
  • 用户体验: 在弹出确认对话框时,要考虑用户体验,避免过度打扰用户。
  • 谨慎使用next(false): 中断导航可能会给用户带来困惑,应该谨慎使用。 尽量使用重定向或其他方式来处理导航。

总结

beforeRouteUpdatebeforeRouteLeave 是 Vue Router 中非常有用的导航守卫,它们允许我们在组件内部对路由变化进行细粒度的控制。 通过合理地使用这两个守卫,我们可以构建更健壮、更用户友好的 Vue 应用。理解它们的工作原理和适用场景,能够帮助你更好地处理各种复杂的路由逻辑。

掌握这两个守卫,让路由控制更加精细。

发表回复

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