Vue 3源码深度解析之:`Vue Router`:`Vue 3`中路由的创建、匹配与导航守卫。

各位观众老爷,晚上好!(或者早上好,中午好,取决于你啥时候看)。今天咱们聊聊Vue 3里面那个“带路党”—— Vue Router。这玩意儿,说白了,就是帮你管理页面跳转的,让你的SPA(Single Page Application,单页面应用)看起来像个多页面应用。

准备好了吗?发车啦!

第一站:路由的创建,从“无”到“有”

首先,得有路由实例,就像你得先有地图,才能知道怎么走。在Vue Router里面,这个“地图”就是createRouter方法创建出来的。

import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('./components/HelloWorld.vue') // 懒加载
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('./components/About.vue')
  },
  {
    path: '/user/:id', // 动态路由
    name: 'User',
    component: () => import('./components/User.vue'),
    props: true // 将路由参数作为props传递给组件
  },
  {
    path: '/:pathMatch(.*)*', // 匹配所有未定义的路由
    name: 'NotFound',
    component: () => import('./components/NotFound.vue')
  }
]

const router = createRouter({
  history: createWebHistory(), // 路由模式,后面会讲
  routes // 路由配置
})

export default router

这段代码做了啥?

  1. 引入必要的东西: createRoutercreateWebHistory是Vue Router提供的,一个用来创建路由实例,一个用来指定路由模式。
  2. 定义路由: routes是一个数组,每个元素都是一个路由对象。每个路由对象里面包含:
    • path: 路径,就是你在浏览器地址栏里看到的。
    • name: 路由的名字,方便你以后跳转的时候用。
    • component: 组件,就是你要显示的页面。这里用了懒加载,只有访问到这个路由的时候才会加载对应的组件,可以提高首屏加载速度。
    • props: 一个很有用的配置,可以让路由参数直接作为组件的props传递。
  3. 创建路由实例: createRouter方法接收一个配置对象,里面包含historyroutes
    • history: 指定路由模式。常用的有createWebHistory(HTML5 History API,地址栏看起来比较正常)和createWebHashHistory(Hash模式,地址栏带#)。
    • routes: 就是我们定义的路由配置。
  4. 导出路由实例: 方便你在其他地方使用。

路由模式大PK:History vs Hash

路由模式决定了你的URL长啥样。Vue Router提供了两种主要的模式:

模式 URL 样式 优点 缺点
createWebHistory /about/user/123 URL看起来更正常,符合传统习惯,利于SEO。 需要服务器端配置,否则刷新页面会404。
createWebHashHistory /#/about/#/user/123 兼容性好,不需要服务器端配置。 URL带#,看起来不太美观,不利于SEO。

选择哪种模式,取决于你的需求和服务器配置。一般来说,如果服务器端可以配置,推荐使用createWebHistory

第二站:路由匹配,找到你的“归宿”

当你访问一个URL的时候,Vue Router会根据你定义的路由配置,找到对应的组件。这个过程就是路由匹配。

  • 静态路由匹配://about这样的路由,直接匹配路径。
  • 动态路由匹配:/user/:id这样的路由,:后面的id是一个参数,可以匹配/user/123/user/456等等。
  • 通配符路由匹配:/:pathMatch(.*)*这样的路由,可以匹配所有未定义的路由。

动态路由参数的获取

动态路由的参数,可以通过$route.params来获取。

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

<script>
import { useRoute } from 'vue-router';
import { defineComponent, computed } from 'vue';

export default defineComponent({
  setup() {
    const route = useRoute();
    const userId = computed(() => route.params.id);

    return {
      userId,
    };
  },
});
</script>

或者,如果你在路由配置中设置了props: true,可以直接作为组件的props传递。

<template>
  <div>
    <h1>User ID: {{ id }}</h1>
  </div>
</template>

<script>
import { defineComponent } from 'vue';

export default defineComponent({
  props: {
    id: {
      type: String,
      required: true
    }
  },
});
</script>

第三站:路由导航,想去哪儿就去哪儿

Vue Router提供了两种主要的导航方式:声明式导航和编程式导航。

  • 声明式导航: 使用<router-link>组件,类似于<a>标签。
<router-link to="/">Home</router-link>
<router-link :to="{ name: 'About' }">About</router-link>
<router-link :to="{ path: '/user/123' }">User 123</router-link>
<router-link :to="{ name: 'User', params: { id: 456 } }">User 456</router-link>

<router-link>组件会自动阻止默认的链接跳转行为,并使用Vue Router的导航机制。

  • 编程式导航: 使用router.pushrouter.replace方法。
import { useRouter } from 'vue-router'
import { defineComponent } from 'vue';

export default defineComponent({
  setup() {
    const router = useRouter()

    const goToHome = () => {
      router.push('/')
    }

    const goToAbout = () => {
      router.push({ name: 'About' })
    }

    const goToUser = (id) => {
      router.push({ path: `/user/${id}` })
    }

    const replaceHome = () => {
      router.replace('/') // 替换当前历史记录
    }

    return {
      goToHome,
      goToAbout,
      goToUser,
      replaceHome
    }
  }
})

router.push会在历史记录中添加一条新的记录,而router.replace会替换当前的记录。

第四站:导航守卫,站岗放哨的“保安”

导航守卫允许你在路由跳转前后做一些事情,比如权限验证,页面统计等等。Vue Router提供了三种类型的导航守卫:

  • 全局守卫: 应用级别的守卫,在所有路由跳转前后都会执行。
    • beforeEach: 在每次路由跳转执行。
    • afterEach: 在每次路由跳转执行。
    • beforeResolve: 在所有组件内守卫和异步路由组件被解析之后,导航被确认之前执行。
  • 路由独享守卫: 只对单个路由生效的守卫。
    • beforeEnter: 在进入该路由执行。
  • 组件内守卫: 在组件内部定义的守卫。
    • beforeRouteEnter: 在进入该组件对应的路由执行,不能访问组件实例this
    • beforeRouteUpdate: 在当前组件对应的路由改变时执行,比如动态路由参数改变。
    • beforeRouteLeave: 在离开该组件对应的路由执行。

全局守卫的用法

router.beforeEach((to, from, next) => {
  // to: 将要进入的路由对象
  // from: 当前导航正要离开的路由对象
  // next: 一个函数,调用它可以让导航进行下去

  console.log('beforeEach: going to', to.name, 'from', from.name)

  // 权限验证示例
  const isAuthenticated = localStorage.getItem('token') // 假设token存在表示已登录

  if (to.name !== 'Login' && !isAuthenticated) {
    // 如果目标路由不是登录页面,并且用户未登录,则跳转到登录页面
    next({ name: 'Login' })
  } else {
    next() // 允许导航
  }
})

router.afterEach((to, from) => {
  // to and from are both route objects.
  console.log('afterEach: gone to', to.name, 'from', from.name)
  // 页面统计示例
  // trackPageView(to.path)
})

router.beforeResolve((to, from) => {
    console.log('beforeResolve: going to', to.name, 'from', from.name)
    return true; // 必须返回 true 或 调用 next()
});

路由独享守卫的用法

const routes = [
  {
    path: '/profile',
    name: 'Profile',
    component: () => import('./components/Profile.vue'),
    beforeEnter: (to, from, next) => {
      // 只有登录用户才能访问profile页面
      const isAuthenticated = localStorage.getItem('token')

      if (!isAuthenticated) {
        next({ name: 'Login' })
      } else {
        next()
      }
    }
  }
]

组件内守卫的用法

<template>
  <div>
    <h1>Profile</h1>
  </div>
</template>

<script>
import { defineComponent } from 'vue';

export default defineComponent({
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this` !
    // 因为当守卫执行前,组件实例还没被创建
    console.log('beforeRouteEnter: going to profile from', from.name)

    // 可以通过 next 回调访问组件实例
    next(vm => {
      // 通过 `vm` 访问组件实例
      console.log('beforeRouteEnter: component instance', vm)
    })
  },
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数 /users/:id 的路由,当 /users/1 和 /users/2 之间跳转的时候,
    // 由于会复用同样的 Profile 组件,因此组件的生命周期钩子不会被调用。
    // 在这种情况下,可以使用 beforeRouteUpdate 钩子函数来监听路由的变化
    console.log('beforeRouteUpdate: route changed from', from.name, 'to', to.name)
    next()
  },
  beforeRouteLeave(to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以用来阻止用户离开,比如在用户正在编辑表单的时候
    const confirmLeave = window.confirm('Are you sure you want to leave?')

    if (confirmLeave) {
      next()
    } else {
      next(false) // 阻止导航
    }
  }
});
</script>

next函数的重要性

在导航守卫中,next函数非常重要。它决定了导航是否继续进行。

  • next(): 允许导航继续进行。
  • next(false): 阻止导航。
  • next('/')next({ path: '/' }): 跳转到指定的路由。
  • next(error): 终止导航,并将错误传递给router.onError()

第五站:高级用法,锦上添花

  • 路由元信息(Meta Fields): 可以在路由配置中添加meta字段,存储一些额外的信息,比如页面标题,权限等等。
const routes = [
  {
    path: '/admin',
    name: 'Admin',
    component: () => import('./components/Admin.vue'),
    meta: {
      requiresAuth: true, // 需要登录才能访问
      title: 'Admin Panel'
    }
  }
]

然后在导航守卫中,可以访问to.meta来获取这些信息。

router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !isAuthenticated) {
    next({ name: 'Login' })
  } else {
    document.title = to.meta.title || 'My App' // 设置页面标题
    next()
  }
})
  • 命名视图(Named Views): 可以在同一个页面中显示多个组件。
<template>
  <div class="home">
    <router-view name="default"></router-view>
    <router-view name="sidebar"></router-view>
  </div>
</template>
const routes = [
  {
    path: '/',
    components: {
      default: () => import('./components/Home.vue'),
      sidebar: () => import('./components/Sidebar.vue')
    }
  }
]
  • 路由别名(Alias): 可以给一个路由设置多个别名。
const routes = [
  {
    path: '/users',
    component: () => import('./components/Users.vue'),
    alias: ['/people', '/members']
  }
]

总结

Vue Router是Vue 3中非常重要的一个组件,它可以帮助你轻松地管理页面跳转。通过学习路由的创建、匹配、导航和导航守卫,你可以构建出复杂的单页面应用。

希望今天的讲解对你有所帮助。记住,多写代码,才能真正掌握!下次再见!

发表回复

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