Vue3——路由管理

路由管理

路由管理是处理页面切换或跳转的关键机制。Vue非常适合开发单页面应用,这种开发模式并不是指应用只包含一个用户界面,而是指在开发层面上采用的一种架构方法,即整个应用只有一个主入口,不同的功能页面通过组件切换来呈现。尽管我们可以利用Vue的动态组件特性来执行组件切换,但这种方式在管理和长期维护上显得尤为烦琐。幸运的是,Vue提供了专门的路由管理工具---VueRouter,它使我们能够更加高效、自然地管理功能页面。

1、Vue Router的安装与简单使用

Vue Router是Vue的官方路由管理工具,它与Vue框架深度整合,为开发单页面应用提供了强大的支撑。VueRouter的主要功能包括:

  • 支持嵌套路由,允许我们以模块化的方式配置和管理路由。
  • 提供路由参数、查询字符串以及通配符的支持,使得路 由配置更加灵活和强大。
  • 内置视图过渡效果,让页面转换更加流畅和自然。
  • 能够精准地控制导航,例如进行条件性的导航或确认导航离开等操作。

1.1、Vue Router的安装

Vue Router的安装与Vue框架本身相似,支持多种引入方式,既可以通过CDN直接引入,也可以使用NPM进行安装。

使用CDN的方式引入,地址如下:

复制代码
https://unpkg.com/vue-router@4

使用NPM进行安装:

shell 复制代码
npm install vue-router@4 --save

注意,安装的Vue Router版本并不要求完全一致,但应确保是4.x.x系列的版本。不同大版本的VueRouter在模块接口和功能上可能存在较大的差异。

1.2、一个简单的Vue Router的使用示例

路由的主要作用是进行页面管理。在实际应用中,我们的操作其实非常直接:将预定义的Vue组件与特定的路由关联起来,并通过路由控制何时以及在何处渲染这些组件。

首先,创建两个简单的示例组件。在项目的components文件夹下,新建两个文件:DemoOne.vue和DemoTwo.vue。以下是这两个组件的示例代码:

DemoOne.vue:

html 复制代码
     <template>
         <h1>示例页面1</h1>
     </template>

DemoTwo.vue:

html 复制代码
     <template>
         <h1>示例页面2</h1>
     </template>

DemoOne和DemoTwo这两个组件作为示例,设计得十分简洁。下面对App.vue文件进行修改:

html 复制代码
<template>
  <h1>HelloWorld</h1>
  <p>
    <!-- route-link是路由跳转组件,用to来指定要跳转的路由-->
    <router-link to="/demo1">页面一</router-link>
    <br/>
    <router-link to="/demo2">页面二</router-link>
  </p>
  <!-- router-view是路由的页面出口,路由匹配到的组件会渲染在此-->
  <router-view></router-view>
</template>

如以上代码所示,router-link组件是一个自定义的链接组件,它比常规的a标签要强大很多,它允许在不重新加载页面的情况下更改页面的URL。router-view用来渲染与当前URL对应的组件,我们可以将其放在任何位置,例如带顶部导航栏的应用,其页面主体内容部分就可以放置为router-view组件,通过导航栏上按钮的切换来替换内容组件。

修改项目中的main.js文件,在其中进行路由的定义与注册,示例代码如下:

js 复制代码
     // 导入Vue框架中的createApp方法
     import { createApp } from 'vue'
     // 导入Vue Router模块中的createRouter和createWebHashHistory方法
     import { createRouter, createWebHashHistory } from 'vue-router'
     // 导入自定义的根组件
     import App from './App.vue'
     // 导入路由需要用到的自定义组件
     import Demo1 from './components/DemoOne.vue'
     import Demo2 from './components/DemoTwo.vue'
     // 挂载根组件
     const app = createApp(App)
     // 定义路由
     const routes = [
       { path: '/demo1', component: Demo1 },
       { path: '/demo2', component: Demo2 },
     ]
     // 创建路由对象
     const router = createRouter({
       history: createWebHashHistory(),
       routes: routes
     })
     // 注册路由
     app.use(router)
     // 进行应用挂载
     app.mount('#app')

运行上述代码,单击页面中的两个切换按钮,可以看到对应的内容组件也会发生切换,如图所示。

2、带参数的动态路由

2.1、路由参数匹配

先编写一个示例的用户中心组件。此组件非常简单,直接通过解析路由中的参数来显示当前用户的昵称和编号。在工程的components文件夹下,新建一个名为UserDemo.vue的文件,在其中编写如下代码:

html 复制代码
<template>
    <h1>姓名:{{$route.params.username}}</h1>
    <h2>id:{{$route.params.id}}</h2>
</template>

如以上代码所示,在组件内部的模板中,可以使用$route属性获取全局的路由对象,路由中定义的参数可以从此对象的params属性中获取到。在main.js中定义路由如下:

js 复制代码
     import User from './components/UserDemo.vue'
     const routes = [
         { path: '/user/:username/:id', component:User }
     ]

在定义路由的路径path时,使用冒号来标记参数,如以上代码中定义的路由路径,username和id都是路由的参数,以下路径会被自动匹配:

js 复制代码
/user/小王/8888

其中,​"小王"会被解析到路由的username属性,8888会被解析到路由的id属性。

现在,运行Vue工程,尝试在浏览器中输入如下格式的地址:

shell 复制代码
http://localhost:5173/#/user/小王/8888

你会看到,页面的加载效果如图所示。

注意,在使用带参数的路由时,对应相同组件的路由在进行导航切换时,相同的组件并不会被销毁再创建,这种复用机制使得页面的加载效率更高。但这也表明,在进行页面切换时,组件的生命周期方法不会被再次调用,如果我们需要通过路由参数来请求数据,之后渲染页面,需要特别注意,不能在生命周期方法中实现数据请求逻辑。例如,修改App.vue组件的模板代码如下:

html 复制代码
<template>
  <h1>HelloWorld</h1>
  <p>
    <router-link to="/user/小王/8888">小王</router-link>
    <br/>
    <router-link to="/user/小李/6666">小李</router-link>
  </p>
  <router-view></router-view>
</template>

修改UserDemo.vue代码如下:

html 复制代码
<template>
    <h1>姓名:{{ $route.params.username }}</h1>
    <h2>id:{{ $route.params.id }}</h2>
</template>

<script setup>
    import {onMounted} from 'vue'
    import {useRouter, useRoute} from 'vue-router'

    const route = useRoute()
    onMounted(()=>{
        alert(`组件加载,请求数据。路由参数为name:${route.params.username} id:${route.params.id}`)
    })
</script>

我们模拟在组件挂载时根据路由参数来进行数据的请求,运行代码可以看到,单击页面上的链接进行组件切换时,User组件中显示的用户名称的用户编号都会实时刷新,但是alert弹窗只有在User组件第一次加载时才会弹出,后续不会再弹。对于这种场景,我们可以采用导航守卫的方式来处理,每次路由参数有更新,都会回调守卫函数,修改UserDemo.vue组件中的JavaScript代码如下:

js 复制代码
<template>
    <h1>姓名:{{ $route.params.username }}</h1>
    <h2>id:{{ $route.params.id }}</h2>
</template>

<script setup>
    import {onMounted} from 'vue'
    import {useRouter, useRoute, onBeforeRouteUpdate} from 'vue-router'

    const route = useRoute()
    onBeforeRouteUpdate((to, from)=> {
        console.log(to, from)
        alert(`组件加载,请求数据。路由参数为name:${to.params.username}  id:${to.params.id}`)
    })
</script>

再次运行代码,当同一个路由的参数发生变化时,也会有alert弹出提示。onBeforeRouteUpdate函数可以注册一个路由守卫,在路由将要更新时调用,路由守卫会传入两个参数,to是更新后的路由对象,from是更新前的路由对象。

2.2、路由匹配的语法规则

在进行路由参数匹配时,Vue Router允许参数内部使用正则表达式来进行匹配。首先,我们来看一个例子。在2.1节中,我们提供了UserDemo组件进行路由示范,将其修改如下:

html 复制代码
<template>
    <h1>用户中心</h1>
    <h1>姓名:{{ $route.params.username }}</h1>
</template>

同时,在components文件夹下新建一个名为UserSetting.vue的文件,在其中编写如下代码:

html 复制代码
<template>
    <h1>用户设置</h1>
    <h2>id:{{ $route.params.id }}</h2>
</template>

我们将UserDemo组件作为用户中心页面来使用,而把UserSetting组件作为用户设置页面来使用,这两个页面所需要的参数不同,用户中心页面需要用户名参数,用户设置页面需要用户编号参数。我们可以在main.js文件中定义路由如下:

js 复制代码
     const routes = [
         { path: '/user/:username', component:User },
         { path: '/user/:id', component:UserSetting }
     ]

你会发现,上述代码中定义的两个路由除参数名不同外,其格式完全一样。这种情况下,我们是无法访问用户设置页面的,所有符合UserSetting组件的路由规则同时也会符合User组件。为了解决这一问题,最简单的方式是加一个静态的前缀路径,例如:

js 复制代码
     const routes = [
         { path: '/user/info/:username', component:User },
         { path: '/user/setting/:id', component:UserSetting }
     ]

这是一个好方法,但并不是唯一的方法。对于本示例来说,用户中心页面和用户设置页面所需要的参数类型有明显的差异,假设用户编号必须是数值,而用户名不能是纯数字,我们可以通过正则约束来实现将不同类型的参数匹配到对应的路由组件,示例代码如下:

js 复制代码
     const routes = [
         { path: '/user/:username', component:User },
         { path: '/user/:id(\\d+)', component:UserSetting }
     ]

这样,​"/user/6666"这样的路由就会匹配到UserSetting组件,而"/user/小王"这样的路由就会匹配到User组件。

在正则表达式中,​"*"符号用于匹配前一个字符的0次或多次出现,​"+"符号用于匹配前一个字符的1次或多次出现。在路由定义中使用这些符号,可以创建具有多级动态参数的路由。例如,在components文件夹下新建一个名为CategoryDemo.vue的示例组件,并编写如下代码:

html 复制代码
<template>
    <h1>类别</h1>
    <h2>{{ $route.params.cat }}</h2>
</template>

在main.js中增加如下路由定义:

js 复制代码
     { path: '/category/:cat*', component:CategoryDemo}

注意,别忘记在main.js文件中对CategoryDemo组件进行引入。当我们采用多级匹配的方式来定义路由时,路由中传递的参数会自动转换成一个数组,例如路由"/category/一级/二级/三级"可以匹配到上面定义的路由,匹配成功后,cat参数为一个数组,其中的数据为["一级","二级","三级"]​。

有时,页面组件并不需要接收所有参数,以用户中心页面为例,当提供了用户名参数时,页面需要渲染用户登录后的状态;而如果没有提供用户名参数,则可能需要渲染用户未登录的状态。在这种情况下,我们可以将username参数定义为可选。配置示例如下:

js 复制代码
     { path: '/user/:username?', component:User }

参数被定义为可选后,路由中不包含此参数时也可以正常匹配到指定的组件。

2.3、路由的嵌套

前面我们定义了很多路由,但是真正渲染路由的地方只有一个,即只有一个出口,这类路由实际上都是顶级路由。在实际开发中,我们的项目可能非常复杂,除根组件中需要路由外,一些子组件中可能也需要路由,Vue Router提供了嵌套路由技术来支持这类场景。

以之前创建的UserDemo组件为例,假设该组件中有一部分用于展示用户的好友列表,这部分同样可以使用组件来实现。首先,在components文件夹下新建一个名为FriendsDemo.vue的文件,并编写以下代码:

html 复制代码
<template>
    <h1>好友列表</h1>
    <h1>好友人数:{{ $route.params.count }}</h1>
</template>

Friends组件只会在用户中心使用,我们可以将其作为一个子路由进行定义。首先,修改UserDemo.vue代码如下:

html 复制代码
<template>
    <h1>用户中心</h1>
    <h1>姓名:{{ $route.params.username }}</h1>
    <router-view></router-view>
</template>

注意,UserDemo组件本身也是由路由管理的,我们在User组件内部使用的标签实际上定义的是二级路由的页面出口。在main.js中定义二级路由如下:

js 复制代码
const routes = [
    {
        path: '/user/:username?', 
        component: User,
        children: [
            {path: 'friends/:count', component: FriendsDemo}
        ]
    },
]

注意,在定义路由时,不要忘记在main.js中引入此组件。之前我们在定义路由时,只使用了path属性和component属性,其实每个路由对象本身也可以定义子路由对象,理论上讲,我们可以根据自己的需要来定义路由嵌套的层数,通过路由的嵌套,可以更好地对路由进行分层管理。如以上代码所示,当我们访问如下路径时,页面效果如图所示。

复制代码
/user/小王/friends/6

3、页面导航

导航通常指的是在不同页面之间的跳转和切换操作。

<router-link>组件是Vue Router中用于实现导航的组件之一。我们可以通过设置其to属性来指定目标路由。除使用<router-link>组件外,还有其他方法可以实现路由控制。任何可以添加交互行为的组件都可以用来进行路由管理。

3.1、使用路由方法

当我们成功地向Vue应用注册路由后,在任何Vue实例中,都可以直接访问路由管理对象。通过调用路由对象的push方法可以向history栈中添加一个新的记录。也就是说,用户可以通过浏览器的返回按钮返回上一个路由URL。

首先,修改App.vue文件代码如下:

html 复制代码
<script setup>
  import {useRouter} from 'vue-router'

  const router = useRouter()
  function toUser() {
    router.push({
      path: '/user/小王'
    })
  }
</script>

<template>
  <h1>HelloWorld</h1>
  <p>
    <el-button type="primary" @click="toUser">用户中心</el-button>
  </p>
  <router-view></router-view>
</template>

如以上代码所示,我们使用按钮组件代替之前的router-link组件,在按钮的单击方法中进行路由的跳转操作。push方法可以接收一个对象,对象中通过path属性配置其URL路径。push方法也支持直接传入一个字符串作为URL路径,代码如下:

js 复制代码
router.push("/user/小王")

我们也可以通过路由名加参数的方式让Vue Router自动生成URL,要使用这种方法进行路由跳转,在定义路由的时候需要对路由进行命名,代码如下:

js 复制代码
const routes = [
    {
        path: '/user/:username?', 
        name: 'user',
        component: User,
    },
]

之后,可以使用如下方式进行路由跳转:

js 复制代码
  function toUser() {
    router.push({
      name: 'user',
      params: {
        username: 'XXXXX'
      }
    })
  }

如果路由需要规范的URL Query参数,可以通过query属性进行设置,示例代码如下:

js 复制代码
//会被处理成 /user?name=xixi
router.push({
	path: '/user',
	query: {
		name: 'xixi'
	}
})

注意,在调用push方法配置路由对象时,如果设置了path属性,则params属性会被自动忽略。push方法本身也会返回一个Pormise对象,我们可以用其来处理路由跳转成功之后的逻辑,示例代码如下:

js 复制代码
  function toUser() {
    router.push({
      name: 'user',
      params: {
        username: 'XXXXX'
      }
    }).then(()=>{
      //组件切换完成后的逻辑
    })
  }

3.2、导航历史控制

当我们使用router-link组件或push方法切换页面时,新的路由实际上会被放入history导航栈中,用户可以灵活地使用浏览器的前进和后退功能在导航栈路由中进行切换。对于有些场景,我们不希望导航栈中的路由增加,这时可以配置replace参数或直接调用replace方法来进行路由跳转,这种方式跳转的页面会直接替换当前的页面,即跳转前页面的路由从导航栈中删除。

js 复制代码
    router.push({
      path: '/user/小王',
      replace: true
    })
    router.replace({
      path: '/user/小王'
    })

Vue Router还提供了一个方法,让我们可以灵活地选择跳转到导航栈中的某个位置,示例代码如下:

js 复制代码
     // 跳转到后1个记录
     router.go(1)
     // 跳转到后3个记录
     router.go(3)
     // 跳转到前1个记录
     router.go(-1)

4、关于路由的命名

在定义路由配置时,除指定path外,我们还可以通过设置name属性来为路由命名。为路由分配一个名称可以带来显著的优势,尤其是相比于直接使用path进行页面跳转,使用名称可以避免硬编码URL,并且它还能自动处理参数编码等相关问题。

4.1、使用名称进行路由切换

与使用path路径进行路由切换类似,router-link组件和push方法都可以根据名称进行路由切换。以前面编写的代码为例,定义用户中心的名称为user,使用如下方法可以直接进行切换:

js 复制代码
    router.push({
      name: 'user',
      params: {
        username: 'XXXXX'
      }
    })

使用router-link组件切换示例如下:

html 复制代码
     <router-link :to="{ name: 'user', params: { username: 'XXXXX' }}">XXXXX</router-link>

4.2、路由视图命名

路由视图命名是指对router-view组件进行命名,router-view组件用来定义路由组件的出口。前面讲过,路由支持嵌套,router-view可以进行嵌套。通过嵌套,允许Vue应用中出现多个router-view组件。但是,对于有些场景,我们可能需要同级展示多个路由视图,例如顶部导航区和主内容区两部分都需要使用路由组件,这时就需要同级使用router-view组件,要定义同级的每个router-view要展示的组件,可以对其进行命名。

修改App.vue文件,将页面的布局分为头部和内容主体两部分,示例代码如下:

html 复制代码
<template>
  <el-container>
    <el-header height="80px">
      <router-view name="topBar"></router-view>
    </el-header>
    <el-main>
      <router-view name="main"></router-view>
    </el-main>
  </el-container>
</template>

在mian.js文件中定义一个新的路由,设置如下:

js 复制代码
const routes = [
    {
        path: '/home/:username/:id',
        components: {
            topBar: User,
            main: UserSetting
        }
    }
]

之前我们定义路由时,一个路径只对应一个组件,其实也可以通过components来设置一组组件,components需要设为一个对象,其中的键表示页面中路由视图的名称,值为要渲染的组件。在上面的例子中,页面的头部会被渲染为User组件,主体部分会被渲染为UserSetting组件,如图所示。

注意,对于没有命名的router-view组件,其名字会被默认分配为default,如果编写组件模板如下:

html 复制代码
<template>
  <el-container>
    <el-header height="80px">
      <router-view name="topBar"></router-view>
    </el-header>
    <el-main>
      <router-view></router-view>
    </el-main>
  </el-container>
</template>

使用如下方式定义路由效果是一样的:

js 复制代码
const routes = [
    {
        path: '/home/:username/:id',
        components: {
            topBar: User,
            default: UserSetting
        }
    }
]

在嵌套的子路由中,也可以使用视图命名路由,对于结构复杂的页面,我们可以先将其按照模块进行拆分,梳理清楚路由的组织关系再进行开发。

4.3、使用别名

别名提供了一种路由路径映射的方式。也就是说,我们可以自由地将组件映射到任意路径上,而不受到嵌套结构的限制。

首先,我们可以尝试为一个简单的一级路由设置别名,修改用户设置页面的路由定义如下:

js 复制代码
const routes = [
    {
        path: '/user/:id(\\d+)',
        component: UserSetting,
        alias: '/setting/:id'
    }
]

下面两个路径的页面渲染效果将完全一样:

js 复制代码
     http://localhost:8080/#/setting/6666/
     http://localhost:8080/#/user/6666/

注意,别名和重定向并不完全一样,别名不会改变用户在浏览器中输入的路径本身,对于多级嵌套的路由来说,我们可以使用别名在路径上对其进行简化。如果原路由有参数配置,一定要注意别名也需要对应地包含这些参数。在为路由配置别名时,alias属性可以直接设置为别名字符串,也可以设置为数组,同时配置一组别名,例如:

js 复制代码
     const routes = [
      { 
      	path: '/user/:id(\\d+)', 
      	component:UserSetting, 
      	alias: ['/setting/:id', '/s/:id'] }
     ]

4.4、路由重定向

重定向也是通过路由配置来完成的,与别名的区别在于,重定向会将当前路由映射到另一个路由上,页面的URL会发生变化。例如,当用户访问路由'/d/1'时,需要要页面渲染'/demo1'路由对应的组件,配置方式如下:

js 复制代码
     const routes = [
       { path: '/demo1', component: Demo1 },
       { path: '/d/1', redirect: '/demo1'},
     ]

redirect也支持配置为路由对象,设置对象的name属性可以直接指定命名路由,例如:

js 复制代码
     const routes = [
       { path: '/demo1', component: Demo1, name:'Demo' },
       { path: '/d/1', redirect: {name : 'Demo'}}
     ]

上面示例代码中都是采用静态的方式配置路由重定向的,在实际开发中,通常会采用动态的方式配置重定向。例如,对于需要用户登录才能访问的页面,当未登录的用户访问此路由时,我们自动将其重定向到登录页面。下面的示例代码模拟了这一过程:

js 复制代码
const routes = [
    {path: '/demo1', component: Demo1, name: 'Demo'},
    {path: '/demo2', component: Demo2},
    {path: '/d', redirect: to => {
        console.log(to) //to是目标路由对象
        //随机数模拟登录状态
        let login = Math.random() > 0.5
        if(login) {
            return {path: '/demo1'}
        } else {
            return {path: '/demo2'}
        }
    }}
]

5、关于路由传参

在执行路由跳转时,可以通过参数传递来处理后续逻辑。在过去,我们习惯使用$route.params来获取传递的参数,尽管这种方式有效,但它让组件与路由配置紧密耦合,影响了组件的复用性。本节将探讨一种更为灵活的路由传参方式------使用属性的方式进行参数传递。

还记得我们编写的用户设置页面是如何获取路由传递的id参数的吗?代码如下:

html 复制代码
     <template>
         <h1>用户设置</h1>
         <h2>id:{{$route.params.id}}</h2>
     </template>
     <script>
     </script>

由于在组件的模板内部之前使用了$route属性,这导致该组件的通用性大大降低。首先,将其所有耦合路由的地方去除掉,修改如下:

html 复制代码
<template>
    <h1>用户设置</h1>
    <h2>id:{{ id }}</h2>
</template>

<script setup>
    defineProps(["id"])
</script>

现在,UserSetting组件能够通过外部传递的属性来实现内部逻辑,后面我们需要做的只是将路由的传参映射到外部属性上。Vue Router默认支持这一功能。路由配置方式如下:

js 复制代码
const routes = [
    {
        path: '/user/:id(\\d+)',
        component: UserSetting,
        props: true
    }
]

在定义路由时,将props设置为true,则路由中传递的参数会自动映射到组件定义的外部属性,使用十分方便。

对于有多个页面出口的同级命名视图,我们需要对每个视图的props单独进行设置,示例如下:

js 复制代码
const routes = [
    {
        path: '/user/:username/:id',
        components: {
            topBar: User,
            default: UserSetting
        },
        props: {topBar: true, default: true}
    }
]

如果组件内部需要的参数与路由本身并没有直接关系,我们也可以将props设置为对象,此时props设置的数据将原样传递给组件的外部属性,例如:

js 复制代码
const routes = [
  { 
  	path: '/user/:id(\\d+)', 
  	component:UserSetting, 
  	props:{id:'000'} },
]

如以上代码所示,此时路由中的参数将被弃用,组件中获取到的id属性值将固定为"000"​。

props还有一种更便捷的使用方式,可以直接将其设置为一个函数,函数返回要传递到组件的外部属性对象,这种方式动态性很好,示例代码如下:

js 复制代码
const routes = [
    {
        path: '/user/:id(\\d+)',
        component: UserSetting,
        props: route => {
            return {
                id: route.params.id,
                other: 'other'
            }
        }
    }
]

6、路由导航守卫

6.1、定义全局的导航守卫

在main.js文件中,前面使用createRouter方法来创建路由实例,此路由实例可以使用beforeEach方法来注册全局的前置导航守卫,之后当有导航跳转触发时,都会被此导航守卫所捕获,示例代码如下:

js 复制代码
const router = createRouter({
    history: createWebHashHistory(),
    routes: routes
})
router.beforeEach((to, from) => {
    console.log(to)//将要跳转的路由对象
    console.log(from)//当前要离开的路由对象
    return fasle    //返回true表示允许此次跳转,返回false表示禁止此次跳转
})

当注册的beforeEach方法返回的是布尔值时,该布尔值用来决定是否允许此次导航跳转,如以上代码所示,所有的路由跳转都将被禁止。

更常见的情况是,在beforeEach方法中返回一个路由配置对象,以此来决定要跳转到的页面。这种方法提供了更高的灵活性。例如,我们可以将登录状态的校验逻辑集成到全局前置守卫中,这使得流程更加方便和一致。以下是一个示例:

js 复制代码
const routes = [
    {
        path: '/user/:id(\\d+)',
        name: 'setting',
        component: UserSetting,
        props: true
    }
]
//创建路由对象
const router = createRouter({
    history: createWebHashHistory(),
    routes: routes
})
router.beforeEach((to, from) => {
    console.log(to)//将要跳转的路由对象
    console.log(from)//当前要离开的路由对象
    if(to.name != 'setting') { //防止无限循环
        return {
            name: 'setting',
            params: {id: '000'}
        }
    }
})

与定义全局前置守卫类似,我们也可以注册全局的导航后置回调。与前置守卫不同的是,后置回调不会改变导航本身,但是其对页面的分析和监控十分有用。示例代码如下:

js 复制代码
     const router = createRouter({
       history: createWebHashHistory(),
       routes: routes // 我们定义的路由配置对象
     })
     router.afterEach((to, from, failure) => {
       console.log("跳转结束")
       console.log(to)
       console.log(from)
       console.log(failure)
     })

路由实例的afterEach方法中设置的回调函数除接收to参数和from参数外,还会接收一个failure参数,通过它开发者可以对导航的异常信息进行记录。

6.2、为特定的路由注册导航守卫

如果只有特定的场景需要在页面跳转过程中实现相关逻辑,我们可以为指定的路由注册导航守卫。有两种注册方式,一种是在配置路由时进行定义,另一种是在组件中进行定义。

在对导航进行配置时,可以直接为其设置beforeEnter属性,示例代码如下:

js 复制代码
const routes = [
  {
    path: "/demo1",
    component: Demo1,
    name: "Demo",
    beforeEnter: (router) => {
      console.log(router);
      return false;
    },
  },
];

如以上代码所示,当用户访问"/demo1"路由对应的组件时,都会被拒绝掉。注意,beforeEnter设置的守卫只有在进入路由时会触发,路由的参数变化并不会触发此守卫。

在编写组件时,我们也可以实现一些方法来为组件定制守卫函数,示例代码如下:

html 复制代码
<script setup>
    import { onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'
    onBeforeRouteUpdate((to, from) => {
        console.log(to, from, "路由参数有更新时的守卫")
    })
    onBeforeRouteLeave((to, from) => {
        console.log(to, from, "离开页面")
    })
</script>

<template>
    <h1>示例页面1</h1>
</template>

注意,在组合式API中并不支持beforeRouterEnter导航前置守卫,如果要实现相关的需求,只能在路由定义时设置beforeEnter回调。在这个函数中,我们可以进行拦截操作,也可以进行重定向操作。需要注意,此方法只有在第一次切换此组件时会被调用,路由参数的变化不会重复调用此方法。beforeRouteUpdate方法在当前路由发生变化时会被调用,例如路由参数的变化等都可以在此方法中捕获到。beforeRouteLeave方法会在当前页面即将离开时被调用。

下面总结一下Vue Router导航跳转的全过程。

  1. 导航被触发,可以通过router-link组件触发,也可以通过push等方法或直接改变URL触发。
  2. 在将要失活的组件中调用beforeRouteLeave守卫函数。
  3. 调用全局注册的beforeEach守卫。
  4. 如果当前使用的组件没有变化,则调用组件内的beforeRouteUpdate守卫。
  5. 调用在定义路由时配置的beforeEnter守卫函数。
  6. 解析异步路由组件。
  7. 导航被确认。
  8. 调用全局注册的afterEach守卫。
  9. 触发DOM更新,页面进行更新。

7、动态路由

到目前为止,我们使用的所有路由都是采用静态配置的方式定义的。也就是说,先在main.js中完成路由的配置,再在项目中进行使用。但某些情况下,我们可能需要在运行的过程中动态地添加或删除路由,Vue Router中也提供了方法支持动态地对路由进行操作。

在Vue Router中,动态操作路由的方法主要有两个:

  • addRoute:用来动态添加一条路由
  • removeRoute:用来动态地删除一条路由。

首先,修改DemoOne.vue文件如下:

html 复制代码
<template>
    <h1>示例页面1</h1>
    <el-button type="primary" @click="click">跳转Demo2</el-button>
</template>
<script setup>
    import { useRouter } from 'vue-router'
    import Demo2 from "./DemoTwo.vue"

    let router = useRouter()
    // 动态添加一个路由
    router.addRoute({
        path: "/new/demo2",
        component: Demo2
    })
    // 跳转到新动态增加的路由
    function click() {
        router.push("/new/demo2")
    }
</script>

我们在DemoOne组件中布局了一个按钮元素,在DemoOne组件创建完成后,使用addRoute方法动态添加了一条路由,当单击页面上的按钮时,切换到DemoTwo组件。

可以尝试一下,如果直接在浏览器中访问"/new/demo2"页面,则会报错,因为此时注册的路由列表中并没有此项路由记录,但是如果先访问"/demo1"页面,再单击页面上的按钮进行路由跳转,则能够正常跳转。

在下面几种场景下会触发路由的删除。

当使用addRoute方法动态地添加路由时,如果添加了重名的路由,旧的就会被删除,例如:

js 复制代码
     router.addRoute({
       path: "/demo2",
       component: Demo2,
       name:"Demo2"
     });
     // 后添加的路由会覆盖前面的
     router.addRoute({
       path: "/d2",
       component: Demo2,
       name:"Demo2"
     });

上述代码中路径为"/demo2"的路由将会被删除。

另外,在调用addRoute方法时,其实会返回一个删除回调,我们可以通过执行此删除回调来直接删除所添加的路由,代码如下:

js 复制代码
     let call = router.addRoute({
       path: "/demo2",
       component: Demo2,
       name: "Demo2",
     });
     // 直接移除此路由
     call();

另外,对于命名过的路由,也可以通过名称将路由删除,示例代码如下:

js 复制代码
     router.addRoute({
       path: "/demo2",
       component: Demo2,
       name: "Demo2",
     });
     router.removeRoute("Demo2");

注意,当路由被删除时,其所有的别名和子路由也会同步被删除。在Vue Router中,还提供了方法来获取现有的路由,例如:

js 复制代码
     // 根据名称获取某个路由是否存在
     console.log(router.hasRoute("Demo2"));
     // 获取所有已注册的路由对象
     console.log(router.getRoutes());

其中,hasRouter方法用来检查当前已经注册的路由中是否包含某个路由,getRoutes方法用来获取包含所有路由的列表。

8、综合示例

使用Vue Router来实现一个具有多个页面的单页应用程序。这个应用将包括以下页面:

  • 首页(Home)
  • 关于我们(About)
  • 产品列表(Products)
  • 产品详情(Product Detail)

在src目录下创建router/index.js:

js 复制代码
import Products from '../components/Products.vue'
import ProductDetail from '../components/ProductDetail.vue'
import Home from '../components/Home.vue';
import About from '../components/About.vue';
import { createRouter, createWebHashHistory } from "vue-router";

const routes = [
    {path: '/', component: Home, name: 'Home'},
    {path: '/about', component: About, name: 'About'},
    {path: '/products', component: Products, name: 'Products'},
    {path: '/product/:id', component: ProductDetail, name: 'ProductDetail'},
]

const router = createRouter({
    history: createWebHashHistory(),
    routes,
})

export default router 

配置Vue应用。在src/main.js中使用Vue Router:

js 复制代码
// src/main.js
import { createApp } from 'vue';
import App from './components/Home.vue';
import router from './router';
const app = createApp(App);
app.use(router);
app.mount('#app');

在src/components目录下创建以下组件。

Home.vue:

html 复制代码
<template>
    <div>
        <h1>Home Page</h1>
        <!-- 使用router-link实现导航-->
         <router-link to="/about">About</router-link><br/>
         <router-link to="/products">Products</router-link>
    </div>
</template>

About.vue:

html 复制代码
<template>
    <div>
        <h1>About Us</h1>
        <p>没有更多内容了...</p>
        <!-- 返回首页 -->
        <router-link to="/">Home</router-link>
    </div>
</template>

Products.vue:

html 复制代码
<template>
    <div>
        <h1>Products</h1>
        <router-link to="/">Home</router-link>
        <!-- 假设有一个产品列表 -->
         <ul>
            <li v-for="product in products" :key="product.id">
                <router-link :to="`/product/${product.id}`">{{ product.name }}</router-link>
            </li>
         </ul>
    </div>
</template>
<script setup>
    import {ref} from 'vue'

    const products = ref([
        {id:1, name: 'Product 1'},
        {id:2, name: 'Product 2'},
    ])
</script>

ProductDetail.vue:

html 复制代码
<template>
    <div>
        <h1>Product Detail</h1>
        <router-link to="/products">Back to Products</router-link>
        <!-- 显示产品详情 -->
         <div v-if="product">
            <h2>{{ product.name }}</h2>
            <!-- 更多产品详情-->
             <p>更多产品详情...</p>
         </div>
    </div>
</template>
<script setup>
    import {computed, ref} from 'vue'
    import {useRoute} from 'vue-router'

    const route = useRoute()
    const product = computed(()=>{
        //根据路由参数获取产品详情
        const id = route.params.id
        return products.value.find(p => p.id == id)
    })

    //假设的产品数据
    const products = ref([
        {id: '1', name: 'Amazing Product 1'},
        {id: '2', name: 'Fantastic Product 2'},
    ])
</script>

App.vue文件:

html 复制代码
     <template>
       <router-view></router-view>
     </template>
相关推荐
skilllite作者5 小时前
Warp 新手极速上手与部署指南
java·前端·笔记·安全·agentskills
遇见~未来5 小时前
第一篇_认识CSS_风格的起点
前端·css
遇见~未来5 小时前
第二篇_CSS核心_盒子_布局_视觉
前端·css
林恒smileZAZ5 小时前
宇宙画布:纯 CSS+JS 实现交互式深空艺术
前端·javascript·css
IT_陈寒5 小时前
Java的finally块居然没执行?这是个巨坑
前端·人工智能·后端
好运的阿财5 小时前
OpenClaw工具拆解之sandboxed_write+sandboxed_edit
前端·ai·ai编程·openclaw·openclaw工具
遇见~未来5 小时前
第四篇_强化视觉_字体_动画_变换
前端·css3
开开心心_Every5 小时前
图片转PDF合并工具,支持扫描仪输入
运维·前端·人工智能·随机森林·edge·pdf·逻辑回归
垦利不5 小时前
TS基础篇
开发语言·前端·typescript