vue 3 第二十九章:路由管理(Vue Router4.x基础知识)

1. Vue Router 的介绍

Vue RouterVue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,可以轻松地构建单页面应用程序。Vue Router 允许我们在应用程序中定义路由,然后根据 URL 来匹配路由,并渲染对应的组件

Vue Router 的核心概念包括路由路由器路由视图导航守卫

  • 路由:指 URL组件之间的映射关系
  • 路由器:指管理所有路由的实例
  • 路由视图:指根据 URL 匹配到的组件
  • 导航守卫:指在路由切换时进行的一些处理操作

2. Vue Router 的安装和使用

2.1. 安装

// npm
npm install vue-router@4

// yarn 
yarn add vue-router@4

2.2. 使用

在 Vue Router 中,我们可以使用 VueRouter 类来创建一个路由器实例,并使用 router-linkrouter-view 组件来定义路由链接和路由视图。

import {
    
     createApp } from 'vue'
import {
    
     createRouter, createWebHistory } from 'vue-router'
import Home from './components/Home.vue'
import About from './components/About.vue'

const router = createRouter({
    
    
  history: createWebHistory(),
  routes: [
    {
    
     path: '/', component: Home },
    {
    
     path: '/about', component: About }
  ]
})

const app = createApp({
    
    })

app.use(router)

app.mount('#app')

在上面的例子中,我们使用 createRouter 函数创建了一个路由器实例,并使用 createWebHistory 函数来创建一个基于浏览器历史记录的路由模式。然后,我们定义了两个路由,分别对应 //about 路径。除了上面这种全局创建之外,更推荐的做法是创建一个 router.js 或 router/index.js 文件。

在模板中,我们可以使用 router-link 组件来定义路由链接,例如:

<template>
  <div>
  	<!--使用 router-link 组件进行导航 -->
    <!--通过传递 `to` 来指定链接 -->
    <!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
    <router-link to="/">Home</router-link>
    <router-link to="/about">About</router-link>
  </div>
</template>

在上面的例子中,我们使用 router-link 组件来定义 //about 两个路由链接。当用户点击链接时,Vue Router 会根据链接的 to 属性来匹配路由,并渲染对应的组件。

在路由视图中,我们可以使用 router-view 组件来渲染路由组件,例如:

<template>
  <div>
  	<!-- 路由出口 -->
  	<!-- 路由匹配到的组件将渲染在这里 -->
    <router-view></router-view>
  </div>
</template>

在上面的例子中,我们使用 router-view 组件来渲染路由组件。当用户访问 / 路径时,Home 组件会被渲染到 router-view 组件中;当用户访问 /about 路径时,About 组件会被渲染到 router-view 组件中。

3. Vue Router 的路由配置和参数传递

3.1. 路由配置

在 Vue Router 中,我们可以使用 VueRouter 类来创建一个路由器实例,并使用 routes 属性来定义路由。每个路由都包含 pathcomponent 两个属性,分别表示 URL 和对应的组件。例如,下面的例子中,我们定义了两个路由,分别对应 //about 路径:

import {
    
     createRouter, createWebHistory } from 'vue-router'
import Home from './components/Home.vue'
import About from './components/About.vue'

const routes = [
   {
    
     path: '/', component: Home },
   {
    
     path: '/about', component: About }
 ]
 
const router = createRouter({
    
    
  history: createWebHistory(),
  routes,
})

export default router;

3.2. 参数传递

在 Vue Router 中,我们可以通过 URL 来传递参数。URL 参数可以通过 propsquery 两种方式传递。

3.2.1. props

使用 props 传递参数时,我们需要在路由配置中定义 props 函数,返回一个对象,对象的属性名对应组件中的 props 属性名,属性值对应 URL 参数名。例如,下面的例子中,我们定义了一个路由,传递了一个 id 参数:

const router = createRouter({
    
    
  history: createWebHistory(),
  routes: [
    {
    
    
      path: '/user/:id',
      component: User,
      props: route => ({
    
     id: route.params.id })
    }
  ]
})

3.2.2. query

使用 query 传递参数时,我们可以在 URL 中添加查询参数,例如 /?id=1。在组件中,我们可以通过 $route.query 来获取查询参数。例如,下面的例子中,我们定义了一个路由,传递了一个 id 参数:

const router = createRouter({
    
    
  history: createWebHistory(),
  routes: [
    {
    
    
      path: '/user',
      component: User,
      props: route => ({
    
     id: route.query.id })
    }
  ]
})

3.3. 访问路由参数

注意:在 setup 形式下,不能使用this.$routerthis.$route,作为替代,我们使用useRouteruseRoute函数:

<script setup lang="ts">
import {
    
     useRouter, useRoute } from 'vue-router'

const router = useRouter()
const route = useRoute()

console.log(router)
console.log(route)
</script>

4. Vue Router 的动态路由和嵌套路由

4.1. 动态路由

Vue Router中,我们可以使用动态路由来匹配不同的 URL。动态路由是指包含参数的路由,参数可以通过 : 来定义。例如,我们可以使用 /user/:id 来定义一个动态路由,其中 :id 表示参数。在组件中,我们可以通过 $route.params 来获取参数。例如,下面的例子中,我们定义了一个动态路由,根据不同的ID渲染不同的用户信息:

const router = createRouter({
    
    
  history: createWebHistory(),
  routes: [
    {
    
    
      path: '/user/:id',
      component: User
    }
  ]
})

在上面的例子中,我们定义了一个 /user/:id 的动态路由,并渲染 User 组件。在 User 组件中,我们可以通过 $route.params.id 来获取 ID

4.2. 嵌套路由

在 Vue Router 中,我们可以使用嵌套路由来组织复杂的页面结构。嵌套路由是指包含子路由的路由。例如,我们可以使用 /user 来定义一个父路由,然后在父路由中包含多个子路由。在组件中,我们可以使用 <router-view> 组件来渲染子路由对应的组件。例如,下面的例子中,我们定义了一个父路由 /user,包含两个子路由 /user/profile/user/posts

const router = createRouter({
    
    
  history: createWebHistory(),
  routes: [
    {
    
    
      path: '/user',
      component: User,
      children: [
        {
    
    
          path: 'profile',
          component: Profile
        },
        {
    
    
          path: 'posts',
          component: Posts
        }
      ]
    }
  ]
})

在上面的例子中,我们定义了一个 /user 的父路由,并渲染 User 组件。在 User 组件中,我们使用 <router-view> 组件来渲染子路由对应的组件。在父路由中,我们使用 children 属性来定义子路由。

5. 路由重定向

在某些情况下,我们可能需要将某个路由重定向到另一个路由,例如将 /a 重定向到 /b。我们可以通过在路由配置中使用 redirect 来实现这一功能。

const router = new VueRouter({
    
    
  routes: [
    {
    
     path: '/a', redirect: '/b' }
  ]
})

我们还可以使用命名路由来实现重定向:

const router = new VueRouter({
    
    
  routes: [
    {
    
     path: '/a', redirect: {
    
     name: 'foo' }}
  ]
})

如果我们需要动态的重定向到一个路由,则可以在 redirect 中使用一个函数来实现:

const router = new VueRouter({
    
    
  routes: [
    {
    
     path: '/a', redirect: to => {
    
    
      // 动态返回重定向目标
      return '/b'
    }}
  ]
})

需要注意的是,如果我们使用了命名路由,那么在重定向时也需要使用命名路由。

6. Vue Router 的导航守卫和跳转控制

Vue Router 中的导航守卫可以用来控制路由的跳转,包括全局导航守卫、路由独享的导航守卫和组件内的导航守卫。

6.1. 全局导航守卫

全局导航守卫会在每次路由跳转时都被调用,包括 beforeEachbeforeResolveafterEach 三个方法。其中,beforeEach 方法用来进行路由跳转前的验证,可以通过 next 方法来进行跳转,例如:

6.1.1. 全局前置守卫(beforeEach)

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。

router.beforeEach((to, from) => {
    
    
  // ...
  // 返回 false 以取消导航
  return false
})

每个守卫方法接收两个参数

  • to: 即将要进入的目标
  • from: 当前导航正要离开的路由

可以返回的值如下:

  • false: 取消当前的导航。如果浏览器的URL改变了(可能是用户手动或者浏览器后退按钮),那么URL地址会重置到from路由对应的地址。
  • 一个路由地址: 通过一个路由地址跳转到一个不同的地址,就像你调用router.push()一样,你可以设置诸如replace: truename: 'home'之类的配置。当前的导航被中断,然后进行一个新的导航,就和from一样。
router.beforeEach(async (to, from) => {
    
    
   if (
     // 检查用户是否已登录
     !isAuthenticated &&
     // ❗️ 避免无限重定向
     to.name !== 'Login'
   ) {
    
    
     // 将用户重定向到登录页面
     return {
    
     name: 'Login' }
   }
 })

可选的第三个参数 next:
进行路由跳转前的验证,并使用next方法来进行跳转。

router.beforeEach((to, from, next) => {
    
    
  if (to.name !== 'Login' && !isAuthenticated) next({
    
     name: 'Login' })
  else next()
})

6.1.2. 全局解析守卫(beforeResolve)

在导航被确认之前,和全局后置守卫(beforeEach)相似,我们也可以注册一个全局解析守卫。这个守卫在全局前置守卫 (beforeEach)、路由独享的前置守卫(beforeRouteEnter)之后调用。

与全局前置守卫不同的是,它会在所有异步路由被解析之后调用,也就是在所有懒加载的组件被加载完之后。

const router = new VueRouter({
    
     ... })

router.beforeResolve((to, from, next) => {
    
    
  /* 在某些情况下,可能需要等待异步组件加载完成后才能执行后续操作 */
  next()
})

注意,该守卫不会像全局前置守卫一样,接收一个回调函数来调用 next 方法。相反,你只需要像 beforeEach 一样调用 next 方法,就可以被解析的异步组件所依赖的所有组件都被解析完毕。

6.1.3. 全局后置钩子(afterEach)

和全局前置守卫、全局解析守卫相似,我们也可以注册一个全局后置守卫。这个守卫在每个路由导航结束后被调用,即在所有的组件渲染完成之后,无论导航是成功的还是被中断的。

const router = new VueRouter({
    
     ... })

router.afterEach((to, from) => {
    
    
  /* 在这里执行一些操作 */
})

常用于页面分析、动态修改页面标题等。
需要注意的是,这里没有 next 方法,也无法改变导航本身。

6.2. 路由独享的导航守卫(beforeEnter)

除了全局前置守卫和全局解析守卫之外,我们还可以在路由配置上直接定义一个 beforeEnter 守卫。这个守卫只会对当前路由起作用。

const router = new VueRouter({
    
    
  routes: [
    {
    
    
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
    
    
        // ...
      }
    }
  ]
})

beforeEnter 的使用方式与全局前置守卫相同,接收三个参数 (to, from, next),并通过调用 next 方法来决定路由的行为。

需要注意的是,beforeEnter 不会像全局解析守卫一样等待异步组件加载完成后再调用,因此在使用异步组件时需要特别注意。

6.3. 组件内的导航守卫

在组件内部,我们也可以定义导航守卫。和全局导航守卫的使用方式相同,我们可以在组件配置中定义 beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave 三个导航守卫。这些守卫不同于全局导航守卫,它们可以访问组件实例 this,因此可以对组件进行更细粒度的控制。

  • beforeRouteEnter: 在路由进入前被调用。此时组件尚未被创建,因此无法访问组件实例 this,但可以通过传递一个回调函数来访问组件实例。
  • beforeRouteUpdate: 在路由更新时被调用(例如,使用相同组件的不同路由)。它可以访问组件实例 this,因此可以在路由更新时更改组件内部的状态。
  • beforeRouteLeave: 在路由离开当前组件时被调用。它可以访问组件实例 this,因此可以在路由离开前执行一些清理操作。
const Foo = {
    
    
  template: `...`,
  beforeRouteEnter (to, from, next) {
    
    
    // 在路由进入前被调用
    // 无法访问组件实例 `this`
    // 但可以通过传递一个回调来访问组件实例
    next(vm => {
    
    
      // 通过 `vm` 访问组件实例
    })
  },
  beforeRouteUpdate (to, from, next) {
    
    
    // 在路由更新时被调用
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    
    
    // 在路由离开当前组件时被调用
    // 可以访问组件实例 `this`
  }
}

需要注意的是,这些导航守卫只对当前组件起作用,因此如果需要在多个组件中共享相同的导航守卫,仍然需要使用全局导航守卫。

猜你喜欢

转载自blog.csdn.net/to_the_Future/article/details/130842635