VUE3基础之路由

0 阅读26分钟

这个东西学过Vue2的懂得都懂,是在单页应用 (SPA) 中将浏览器的 URL 和用户看到的内容绑定起来。当用户在应用中浏览不同页面时,URL 会随之更新,但页面不需要从服务器重新加载,也就是浏览器不会刷新。我们以前用a标签跳转页面查看不同的内容时页面会刷新一下,用户体验不是很好,而路由可以帮助我们通过不同的url在同一个页面不用刷新就可以展示出不同的内容,用户体验极好。

路由的基本使用

前提准备

我们先来搭一个基本的使用场景。

<template>
  <div class="screen-view">
    <!-- 导航区 -->
    <div class="header">
      <a href="">路由1</a>
      <a href="">路由2</a>
      <a href="">路由3</a>
    </div>
    <!-- 展示区 -->
    <div class="container"></div>
  </div>
</template>

1749478915932.png

到时候我们点击哪个路由,对应的页面就会在下面的方框里面展示,现在我们要先去安装一下路由器。

安装vue-router

要想使用路由器,我们要先安装vue-router,当然也可以在创建Vue3项目的时候直接选择配置上这个vue-router

npm i vue-router

配置路由器

安装好以后我们要在src文件夹下创建一个新的文件夹用来配置路由器,这个文件夹一般命名为router,然后里面创建一个index.ts文件用来创建并配置路由器。

1749471788758.png

然后我们就在这个文件里面进行一些配置,首先肯定是引入创建路由器的函数,Vue3跟Vue2不一样的地方就是Vue3把每一个功能模块都变成了单独的函数,比如创建响应式数据就是ref或者reactive函数,创建监视器就是watch函数,这里创建路由器也是如此,从vue-router引入createRouter函数。

import { createRouter } from 'vue-router'

我们就通过createRouter函数去创建一个路由器,createRouter函数里面接收一个参数对象,去配置我们这个路由器,其中有一个属性叫做routes,这个属性是一个数组,里面存放的就是不同的路由信息。routes数组的每一项都是一个对象,存放的是每一个路由的信息,其中有最重要的有三个属性,path是路由的访问路径,name是路由的名称,component是该路由显示的组件内容。

注意一下,router是路由器,上面存放着整个路由器的信息和路由器的相关操作。route是路由,存放着某个路由的所有信息。

const router = createRouter({
  routes: [
    {
      path: '/route1',
      name: "routeName1",
      component: () => import("@/views/route1/index.vue"),
    }
  ]
})

其实这个时候是会报错的,因为配置路由器的时候还少了一项,就是路由器的工作模式,路由器有两种工作模式,hash模式和history模式。这里我们用history模式,我们要从vue-router引入一个函数createWebHistory

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

然后在配置路由器的时候添加一个属性history,然后把createWebHistory函数赋值给它,这么一来路由器的工作模式就配置好了,此时这个代码就不会报错了。完整的代码如下:

// 创建一个路由器并且暴露出去

// 第一步:引入 vue-router
import { createRouter, createWebHistory } from 'vue-router'

// 第二步:创建一个路由器
const router = createRouter({
  // 第三步:配置路由器
  history: createWebHistory(),  // 路由器的工作模式
  routes: [  // 一个一个的路由信息
    {
      path: '/route1',
      name: "routeName1",
      component: () => import("@/views/route1/index.vue"),
    }
  ]
})

// 将路由器暴露出去
export default router

将路由器使用到Vue上

我们创建配置完路由器以后得用上啊,不然白配置了,我们需要在创建Vue应用的时候,让这个路由器使用到Vue应用上,所以我们要在main.ts里面引入这个路由器,并且通过use方法让Vue使用这个路由器。

import { createApp } from 'vue'  // createApp用来创建VUE应用的
import App from './App.vue'  // 这是VUE应用的根组件
import router from './router'  // 引入路由器

const app = createApp(App)  // 创建一个名字叫app的VUE应用且根组件为App

app.use(router)  // VUE应用使用路由器

app.mount('#app')  // 将VUE应用挂在到一个id为app的容器里,这个容器就在前面说的入口文件index.html里

配置RouterView

这个时候我们先把代码补充完,原本三个路由按钮,所以我们也要创建三个路由信息出来,并创建好对应的组件。

1749559273038.png

对应组件的内容为我是路由1,我是路由2,我是路由3。

route1/index.vue

<template>
  <div>我是路由1</div>
</template>

<script lang="ts" setup></script>

那现在问题来了,路由器创建好了,路由信息和路由对应的组件也都创建好了,那是不是就可以使用了呢?

我们来试一试,我们给页面的url后面加上路由的路径。

1749479979604.png

会发现展示区内并没有我们想象的展示出对应的内容,是不是还差点什么呢?其实在我们在给页面的url后面加上/route的时候路由器已经监视到url的变化,它就会去找routes里的路由信息,看看是否有这个路径,然后发现路由信息里有这个路径,也找到了这个路径对应的组件内容component,接下来就该想这个组件内容应该放在哪,可是这个时候因为我们没有配置,所以此时路由器也懵了,它也不知道这个组件内容应该展示在哪里。

我们看前面的代码:

<template>
  <div class="screen-view">
    <!-- 导航区 -->
    <div class="header">
      <a href="route1">路由1</a>
      <a href="route2">路由2</a>
      <a href="route3">路由3</a>
    </div>
    <!-- 展示区 -->
    <div class="container">
    </div>
  </div>
</template>

虽然我们注释了展示区,但是这只是给我们提示的,它并没有实际作用,页面此时不知道路由对应的组件内容应该展示在哪,所以我们需要从vue-router上引入一个新的东西RouterView路由器视图,然后把它放在我们想要展示内容的地方。

<template>
  <div class="screen-view">
    <!-- 导航区 -->
    <div class="header">
      <a href="route1">路由1</a>
      <a href="route2">路由2</a>
      <a href="route3">路由3</a>
    </div>
    <!-- 展示区 -->
    <div class="container">
      <RouterView></RouterView>
    </div>
  </div>
</template>
<script lang="ts" setup>
import { RouterView } from "vue-router";
</script>

这个时候路由器就知道把内容展示在哪里了,我们再试一下:

1749480420914.png

此时就可以实现路由的基本使用了。

但是我们不能每次切换内容,都要去手动修改url吧,这时候我们再从vue-router上引入一个RouterLink,用它来替换我们前面前面代码上的a链接,再把href属性换成to属性,to属性的值就是我们前面配置路由信息的时候写的路由路径,此时就可以实现点击链接进行切换内容了。 我们也可以去实现点击的时候让其修改样式,RouterLink有一个属性active-class,值是一个类名,效果是当该链接激活的时候,会实现该类名的样式。

<template>
  <div class="screen-view">
    <!-- 导航区 -->
    <div class="header">
      <RouterLink to="/route1" active-class="activeClass">路由1</RouterLink>
      <RouterLink to="/route2" active-class="activeClass">路由2</RouterLink>
      <RouterLink to="/route3" active-class="activeClass">路由3</RouterLink>
    </div>
    <!-- 展示区 -->
    <div class="container">
      <RouterView></RouterView>
    </div>
  </div>
</template>
<script lang="ts" setup></script>
<style scoped>
.screen-view {
  height: 100%;
}
.header {
  display: flex;
  justify-content: space-around;
  height: 30px;
  margin-bottom: 10px;
}
a {
  display: inline-block;
  height: 30px;
  width: 100px;
  background-color: pink;
  text-align: center;
  color: #fff;
  text-decoration: none;
  border-radius: 10px;
  line-height: 30px;
}
.container {
  margin-left: 100px;
  margin-right: 100px;
  height: calc(100% - 40px);
  box-sizing: border-box;
  border: 5px solid #ccc;
  border-radius: 10px;
  height: 700px;
}

.activeClass {
  background-color: yellowgreen;
  text-align: center;
  color: #fff;
}
</style>

1749481222593.png

而且这个RouterLinea标签的样式是通用的,所以此时作用在a标签的样式也可以直接给RouterLink去用,这个时候就简单完成了一个导航栏的基本功能。不过实际项目中我们会用Element Ui组件去实现这个RouterLink导航栏效果,但是该配置的路由信息还是要配置的,RouterView也是要写上的。

哦对了,这个RouterViewRouterLink也是不用引入的,这里写引入只是为了知道这两个东西从哪里来的。

路由器的工作模式

我们前面讲配置路由器的时候要配置一个属性叫做history,这是代表路由器的工作模式,路由器有两个工作模式,一个是history(这里是工作模式的名称而非前面说的属性),另外一个叫做hash

history模式

history模式是通过从vue-router引入的createWebHistory函数配置,这一点前面内容讲了。

// 创建一个路由器并且暴露出去

// 第一步:引入 vue-router
import { createRouter, createWebHistory } from 'vue-router'

// 第二步:创建一个路由器
const router = createRouter({
  // 第三步:配置路由器
  history: createWebHistory(),  // 路由器的工作模式
  routes: [  // 一个一个的路由信息
    ...
  ]
})

export default router

history模式的优点就是url更加美观,不会携带#,更接近传统网站的url

我们来截个图看一下。

1749557447665.png

没有携带#,原本urlhttp://localhost:5173,选择路由以后直接在后面拼接上http://localhost:5173/route1,就看着比较美观。

但是history工作模式有一个问题,项目后期上线以后,需要服务端配合处理路径问题,否则刷新会有404报错。(以后会讲)

hash模式

hash模式是通过从vue-router引入的createWebHashHistory函数配置。

// 创建一个路由器并且暴露出去

// 第一步:引入 vue-router
import { createRouter, createWebHashHistory } from 'vue-router'

// 第二步:创建一个路由器
const router = createRouter({
  // 第三步:配置路由器
  history: createWebHashHistory(),  // 路由器的工作模式
  routes: [  // 一个一个的路由信息
   ...
  ]
})

export default router

hash模式的优点是不需要服务器去处理路径,兼容性更好。但是问题也是有的,就是url上面会有一个#,比较影响美观。

1749557754160.png

原本urlhttp://localhost:5173/#/,选择路由以后直接在后面拼接上http://localhost:5173/#/route1,因为有#所以看着没那么美观,而且在SEO优化上比较差。

RouterLink中to属性的两种写法

字符串写法

我们前面在讲路由器的基本使用的时候,用RouterLink进行路由的切换,去展示to属性指定的路由路径所对应的组件内容,那时候我们的to属性值是一个字符串,指的是路由信息的路径。

<RouterLink to="route1" active-class="activeClass">路由1</RouterLink>

切换到此路由的时候,RouterView就会展示该路径所对应的组件内容。

对象写法

其实to属性还有一种写法,这种写法下的to属性的值是一个对象,里面其中有一个属性叫做path,这个path和配置路由时候的path对应,这个path的值也是一个字符串,指的就是路由的路径。

<RouterLink :to="{ path: '/route1' }" active-class="activeClass">路由1</RouterLink>

此时切换到该路由的时候也会展示该路由路径对应的组件内容。

这么一看是不是觉得还是字符串写法比较方便,其实不然,对象的写法里面可以做很多操作,比如给路由传参,或者设置动态路由,这一点我们下面会讲到。

命名路由

命名路由就是给路由去命名,通过路由的名称去展示路由对应的组件内容。

我们前面讲配置路由器的路由信息routes的时候,讲了routes是一个数组,里面的每一项都是一个对象,指的是不同的路由信息,对象里面有三个最重要的属性分别为pathnamecomponent,其中path是路由的路径,name是路由的名称,component是路由路径对应的组件内容,pathcomponent我们都讲了它的作用了,那name的作用是什么呢?其实就是通过路由的名称去展示路由对应的内容。

const router = createRouter({
  // 第三步:配置路由器
  history: createWebHashHistory(),  // 路由器的工作模式
  routes: [  // 一个一个的路由信息
    {
      path: '/route1',  // 路由路径
      name: "routeName1",
      component: () => import("@/views/route1/index.vue"),  // 路由路径对应的组件内容
    },
  ]
})

我们讲to属性的对象写法的时候,不是觉得对象写法比字符串写法要麻烦吗,其实这里就可以用到name属性了,这个时候我们不一样非要写{path: '/route1'}才可以展示route1路由对应的组件内容,可以用路由的name属性。to属性的对象值里也有一个name属性,和路由的name属性是对应的,就是路由的名称,此时切换到该路由的时候就会展示该路由的名称所对应的组件内容。

<RouterLink :to="{ name: 'routeName2' }" active-class="activeClass">路由2</RouterLink>

嵌套路由

我们前面做所的页面分为导航区和展示区,其中蓝色框里的是导航区,红色框里的是展示区,根据选择导航区里不同的路由,展示区会展示路由对应的内容。

1749560412871.png

那如果我们想在展示区里面再搞一个导航,再搞一个展示区呢?这个时候该怎么做,我先在route1的组件内容里面去搭建这么一个情形。

route1/index.vue

<template>
  <div class="screen-view">
    <!-- 嵌套的导航区 -->
    <div class="left-navmenu">导航区</div>
    <!-- 嵌套的展示区 -->
    <div class="right-container">展示区</div>
  </div>
</template>

<script lang="ts" setup></script>
<style scoped>
.screen-view {
  display: flex;
  height: 100%;
}

.left-navmenu {
  width: 260px;
  height: 100%;
  margin-right: 10px;
}

.right-container {
  width: calc(100% - 270px);
  height: 100%;
}
</style>

当前页面为下面这样。

1749567160284.png

我们想在route1的页面里嵌套路由,就要用到路由信息配置的另外一个属性children,该属性是一个数组,里面存放的跟routes一样都是路由信息,只不过是该页面下嵌套的路由信息。

注意事项:这里嵌套的路由的路径path不需要写/了,到时候拼接在父路由的后面的时候会自己加上/,如果这里写/,就不是拼接在父路由后面,而是直接替换父路由了。

const router = createRouter({
  // 第三步:配置路由器
  history: createWebHashHistory(),  // 路由器的工作模式
  routes: [  // 一个一个的路由信息
    {
      path: '/route1',
      name: "routeName1",
      component: () => import("@/views/route1/index.vue"),
      children: [
        {
          path: 'news1',  // 嵌套的路由不需要写 /
          name: 'newsName1',
          component: () => import('@/views/route1/news1/index.vue')
        },
        {
          path: 'news2',
          name: 'newsName2',
          component: () => import('@/views/route1/news2/index.vue')
        },
        {
          path: 'news3',
          name: 'newsName3',
          component: () => import('@/views/route1/news3/index.vue')
        },
      ]
    },
    {
      path: '/route2',
      name: "routeName2",
      component: () => import("@/views/route2/index.vue"),
    },
    {
      path: '/route3',
      name: "routeName3",
      component: () => import("@/views/route3/index.vue"),
    },
  ]
})

然后我们再去创建嵌套路由对应展示的组件。

1749646523875.png

然后用RouterLinkRouterView去配置嵌套路由的跳转链接和展示区,此时RouterLinkto属性该怎么写呢?是写/news1吗?不能这么写嗷,应该写/route1/news1,如果直接写/news1,就会把route1给覆盖掉,相当于urlhttp://localhost:5173/#/news1了。

<template>
  <div class="screen-view">
    <!-- 嵌套的导航区 -->
    <div class="left-navmenu">
      <RouterLink to="/route1/news1" active-class="activeClass">新闻1</RouterLink>
      <RouterLink to="/route1/news2" active-class="activeClass">新闻2</RouterLink>
      <RouterLink to="/route1/news3" active-class="activeClass">新闻3</RouterLink>
    </div>
    <!-- 嵌套的展示区 -->
    <div class="right-container">
      <RouterView></RouterView>
    </div>
  </div>
</template>

<script lang="ts" setup></script>
<style scoped>
.screen-view {
  display: flex;
  height: 100%;
}

.left-navmenu {
  width: 260px;
  height: 100%;
  margin-right: 10px;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
}

a {
  display: inline-block;
  height: 30px;
  width: 100px;
  background-color: pink;
  text-align: center;
  color: #fff;
  text-decoration: none;
  border-radius: 10px;
  line-height: 30px;
}

.activeClass {
  background-color: yellowgreen;
  text-align: center;
  color: #fff;
}

.right-container {
  width: calc(100% - 270px);
  height: 100%;
}
</style>

1749646881996.png

1749646911919.png

这样嵌套路由的基本功能就实现了。

路由传参

路由也是可以传参数过去的,到时候跳转到新页面的时候会自动在url后面拼接上这个参数。

路由传参有两种形式,一种是query传参,还有一种是params传参。

query传参

如果我们RouterLinkto属性值的写法是字符串形式的,就直接在后面用?拼接上我们要传的参数,如果多个参数就用&隔开,这个都懂吧,就是url后面参数的写法。

<RouterLink to="/route1/news1?a=1&b=2&c=3" active-class="activeClass">嵌套路由1</RouterLink>

这个时候跳转到该页面的时候,url上面就会拼接上我们传的参数。

1749649091440.png

传完参数以后我们在这个页面怎么接收这个参数呢?前面讲配置路由器的时候说了,router是路由器,里面是整个路由器的信息。route是路由,里面是该路由的所有信息。所以我们这里要接收路由上的参数,只要从route上面拿就可以了,我们先从vue-router上面引入一个useRoute函数,我们执行这个函数并把它赋值给一个变量打印来看看是什么东西。

route1/news1/index.vue

<template>
  <div>新闻内容1</div>
</template>

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

let route = useRoute();
console.log("route", route);
</script>

1749645129537.png

可以看到打印出来的是一个Proxy对象,这个是不是很熟悉,就是reactive函数创建的复杂类型响应式数据,里面的Target属性就是对象具体的值,这个对象就是路由信息对象route,里面都是该路由的信息,我们可以看到里面有一个query属性,这里面就是我们要接收的query传参传来的参数。

所以我们可以直接通过route.query的方式获取到传来的参数。

这是to属性值为字符串写法的时候query传参方式,如果参数很多的时候我们可能就要写好长一串,如果再传变量的话,加上反引号,变量名什么的就更长了,就很不美观。

<RouterLink :to="`/route1/test1?a=${a}&b=${b}&c=${c}`" active-class="activeClass">嵌套路由1</RouterLink>

所以一般传参的情况下,我们就会用到to属性值为对象的写法。我们前面在讲to属性值为对象的时候,讲过两个属性,一个是path属性,指的是路由路径,点击该路由的时候就会展示到路径对应的组件内容,一个是name属性,指的是路由名称,点击该路由的时候就会展示到路由名词对应的组件内容,现在再讲一个属性query,这个属性值为一个对象,里面存放的就是我们query传参要传的参数。

<RouterLink
  :to="{ path: '/route1/test1', query: { a: 1, b: 2, c: 3 } }"
  active-class="activeClass"
  >嵌套路由1</RouterLink
>

这样看起来就比较简单明了了。

params传参

讲完query传参我们现在来讲params传参,还是先用to属性值为字符串的写法来讲。

我们前面讲query传参的时候,参数都是键值对的形式且用&隔开,但是params传参的时候就不用写键了,直接写值,如果多个就用/隔开。

<RouterLink to="/route1/news1/yueliang/月亮/18" active-class="activeClass">嵌套路由1</RouterLink>

这样看着是不是比query传参好一点,最起码不用写键了,但是这么写的话,这些参数是不是很像路由的路径啊,就像是子路由一样,我们在页面上看一下会有什么反应。

1749730665609.png

我们会发现刚进入父路由还没有点击子路由的时候,控制台就有警告了No match found for location with path "/route1/news1/yueliang/月亮/18",翻译过来就是没有找到/route1/news1/yueliang/月亮/18这个路由,而且当我们点击这个子路由的时候,也没有展示对应的页面。

1749730952724.png

看起来路由器这里也是把传的参数当成路径去看了,所以要想这么传参,我们还得去路由信息那里做一些配置。

我们需要在被传参的路由上,这里也就是子路由news1上做一些修改,在这个路由的路径属性path值后面通过/去拼接一些东西,这个东西就是刚才我们传参的键了,而且为了防止路由器觉得拼接的是路径,我们还得在键前面加上:

router/index.ts

const router = createRouter({
  // 第三步:配置路由器
  history: createWebHashHistory(),  // 路由器的工作模式
  routes: [  // 一个一个的路由信息
    {
      path: '/route1',
      name: "routeName1",
      component: () => import("@/views/route1/index.vue"),
      children: [
        {
          path: 'news1/:enName/:cnName/:age',  // 被传参的路由路径后面拼接上要传参的键
          name: 'newsName1',
          component: () => import('@/views/route1/news1/index.vue')
        },
        {
          path: 'news2',
          name: 'newsName2',
          component: () => import('@/views/route1/news2/index.vue')
        },
        {
          path: 'news3',
          name: 'newsName3',
          component: () => import('@/views/route1/news3/index.vue')
        },
      ]
    },
    {
      path: '/route2',
      name: "routeName2",
      component: () => import("@/views/route2/index.vue"),
    },
    {
      path: '/route3',
      name: "routeName3",
      component: () => import("@/views/route3/index.vue"),
    },
  ]
})

1749731321279.png

这个时候页面就是可以正常展示的了,这就相当于传参的时候在RouterLinkto属性上只写参数的值,而把参数的名称写在配置路由信息的地方。

讲完传参,现在讲讲如何取参数,我们在讲取query传的参数的时候,是用到了useRoute函数的,它的返回值就是该路由的信息对象,里面存放着该路由的所有信息,当时打印这个路由信息对象的时候,里面其中一个属性是query,这就是query传的参数,还有一个属性params,这里就是params传的参数了,属性params也是一个对象,里面就是参数的键值对。

route1/news1/index.vue

<template>
  <div>新闻内容1</div>
</template>

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

let route = useRoute();
console.log("route", route);
</script>

1749731826717.png

讲完to属性值是字符串的情况,就要讲to属性值为对象的情况了,还是一样,因为如果传参太多且是变量的话,字符串的形式就要写好长了。

<RouterLink :to="`/route1/news1/${enName}/${cnName}/${age}`" active-class="activeClass">嵌套路由1</RouterLink>

所以用对象的写法,这样可读性也比较高,讲到现在也应该猜得出来对象的写法怎么写了,query传参的时候用的是to属性值对象的query属性,那么params传参的时候自然要用的就是to属性值对象的params属性了。按照query传参的写法就是to属性值里面有一个path属性存放路径,一个params属性存放参数的键值对,但是事实真的如此吗?

<RouterLink
  :to="{ path: '/route1/news1', params: { enName: 'yueliang', cnName: '月亮', age: 18 }}"
  active-class="activeClass"
  >嵌套路由1</RouterLink
>

1749732450049.png

会发现当我们点击子路由后,页面没展示了,而且url上面也没有参数了,后台还告警了Path "/route1/news1" was passed with params but they will be ignored. Use a named route alongside params instead,这句话的意思是,当我们用params传参的时候会忽略path,要使用命名路由。所以此时的写法应该是:

<RouterLink
  :to="{ name: 'newsName1', params: { enName: 'yueliang', cnName: '月亮', age: 18 }}"
  active-class="activeClass"
  >嵌套路由1</RouterLink
>

1749732665570.png

此时页面就可以正常展示了,而且url后面也会拼接上参数。

这里还有两个个注意点:1.params传参的时候,不能传对象或数组,会报错。2.如果我们用to属性值为对象的写法,然后又不配置路由信息,就是说在router/index.ts文件上配置路由路径的时候不在后面拼接上参数的键,页面可以正常展示,但是不会传参,如果to属性值为字符串,就会直接报错,因为路由器会把参数当成路径去分析,这个前面讲过了, 这里就不展示了,可以自己去试一试。

现在还有一个情况,就是如果我突然不想传某个参数了呢?我们试一下,不传参数age

<RouterLink
  :to="{ name: 'newsName1', params: { enName: 'yueliang', cnName: '月亮' }}"
  active-class="activeClass"
  >嵌套路由1</RouterLink
>

1749733320406.png

页面会直接报错,说你缺少一个必要的参数age,如果遇到这种情况,想实现这种可传可不传的参数的时候,我们只需要在配置路由信息的时候,在给路径后面拼接参数名的时候,在后面加上一个?就可以了,像ts那样。

router/index.ts

const router = createRouter({
  // 第三步:配置路由器
  history: createWebHashHistory(),  // 路由器的工作模式
  routes: [  // 一个一个的路由信息
    {
      path: '/route1',
      name: "routeName1",
      component: () => import("@/views/route1/index.vue"),
      children: [
        {
          path: 'news1/:enName/:cnName/:age?',  // 被传参的路由路径后面拼接上要传参的键
          name: 'newsName1',
          component: () => import('@/views/route1/news1/index.vue')
        },
        {
          path: 'news2',
          name: 'newsName2',
          component: () => import('@/views/route1/news2/index.vue')
        },
        {
          path: 'news3',
          name: 'newsName3',
          component: () => import('@/views/route1/news3/index.vue')
        },
      ]
    },
    {
      path: '/route2',
      name: "routeName2",
      component: () => import("@/views/route2/index.vue"),
    },
    {
      path: '/route3',
      name: "routeName3",
      component: () => import("@/views/route3/index.vue"),
    },
  ]
})

这个时候就可以正常展示页面了。

1749733500768.png

区别

params传参的时候不能用路径path去展示页面,只能用命名路由的方式,并且不能传数组或者对象,而query传参的时候没有这些限制。而且params传参需要在配置路由信息的时候提前在规则中占位。

路由的props配置

这里的props配置可不是父子组件传值了,指的是路由规则的props配置。它有三个写法:

1.布尔值写法

我们前面光讲怎么传参了,但是还没有用,所以我们现在修改下代码让其用到我们传的参数。

route1/index.vue 父页面

这里修改了一下英文名,不用拼音了,好丢人......

<template>
  <div class="screen-view">
    <!-- 嵌套的导航区 -->
    <div class="left-navmenu">
      <RouterLink
        :to="{ name: 'newsName1', params: { enName: 'Moon', cnName: '月亮', age: 18 } }"
        active-class="activeClass"
        >嵌套路由1</RouterLink
      >
      <RouterLink to="/route1/news2" active-class="activeClass">嵌套路由2</RouterLink>
      <RouterLink to="/route1/news3" active-class="activeClass">嵌套路由3</RouterLink>
    </div>
    <!-- 嵌套的展示区 -->
    <div class="right-container">
      <RouterView></RouterView>
    </div>
  </div>
</template>

route1/news1/index 子页面

接收参数以后在页面上进行展示:

<template>
  <div>中文:{{ route.params.cnName }}</div>
  <div>英文:{{ route.params.enName }}</div>
  <div>年龄:{{ route.params.age }}</div>
</template>

<script lang="ts" setup>
import { useRoute } from "vue-router";
let route = useRoute();
console.log(route);
</script>

1750162025928.png

可以看到页面是可以正常展示的,但是这样用起来要写好长一串就比较麻烦,下面还有配置一堆东西,所以这里就可以用到路由的props配置了。

既然是路由规则的props配置,那自然配置在路由信息上,我们在讲配置路由信息的时候,已经讲了pathnamecomponent了,现在又多了一个props,第一个写法是布尔值写法,所以props的值为一个布尔值。

router/index.ts

const router = createRouter({
  // 第三步:配置路由器
  history: createWebHashHistory(),  // 路由器的工作模式
  routes: [  // 一个一个的路由信息
    {
      path: '/route1',
      name: "routeName1",
      component: () => import("@/views/route1/index.vue"),
      children: [
        {
          path: 'news1/:cnName/:age/:enName',
          name: 'newsName1',
          component: () => import('@/views/route1/news1/index.vue'),
          props: true
        },
        {
          path: 'news2',
          name: 'newsName2',
          component: () => import('@/views/route1/news2/index.vue')
        },
        {
          path: 'news3',
          name: 'newsName3',
          component: () => import('@/views/route1/news3/index.vue')
        },
      ]
    },
    {
      path: '/route2',
      name: "routeName2",
      component: () => import("@/views/route2/index.vue"),
    },
    {
      path: '/route3',
      name: "routeName3",
      component: () => import("@/views/route3/index.vue"),
    },
  ]
})

加了propstrue以后就相当于什么,相当于父组件route1给子组件news1传值cnNameenNameage了,那我们就可以直接在子组件news1上用defineProps函数去接收值了。这一点我们在讲父子组件传值的时候已经讲过了。

route1/news1/index.vue

<template>
  <div>中文:{{ cnName }}</div>
  <div>英文:{{ enName }}</div>
  <div>年龄:{{ age }}</div>
</template>

<script lang="ts" setup>
defineProps(["cnName", "enName", "age"]);
</script>

1750163048083.png

此时页面也可以正常展示,但是这个用法只限制params传参。

我们改成query传参看一下:

router/index.ts

{
  path: 'news1',
  name: 'newsName1',
  component: () => import('@/views/route1/news1/index.vue'),
  props: true
},

route1/news1/index.vue

<RouterLink
  :to="{ name: 'newsName1', query: { enName: 'Moon', cnName: '月亮', age: 18 } }"
  active-class="activeClass"
  >嵌套路由1</RouterLink
>

1750163582885.png

可以看到页面是没有展示了。所以这种propstrue的用法只能用于params传参。

2.函数写法

那怎么用到query传参呢?这就用到第二种写法函数写法了,此时props的值不再是一个布尔值true了,而是要把props写成一个函数,然后返回一个对象,对象里的值会作为props传给路由组件。

router/index.ts

{
  path: 'news1',
  name: 'newsName1',
  component: () => import('@/views/route1/news1/index.vue'),
  props() {
    return { a: 1, b: 2, c: 3 }
  }
},

此时我们就可以用defineProps去接收这些参数了。

route1/news1/index.vue

<template>
  <div>中文:{{ a }}</div>
  <div>英文:{{ b }}</div>
  <div>年龄:{{ c }}</div>
</template>

<script lang="ts" setup>
defineProps(["a", "b", "c"]);
</script>

1750163995315.png

此时页面上也可以正常展示,但是这跟我们说的query传参有什么关系呢?其实props函数接收一个参数,我起名为route,因为这个参数route就是我们的路由信息对象。

router/index.ts

{
  path: 'news1',
  name: 'newsName1',
  component: () => import('@/views/route1/news1/index.vue'),
  props(route) {
    console.log(route);
    return { a: 1, b: 2, c: 3 }
  }
},

1750164390881.png

因为props函数返回一个对象,对象里的值会作为props去传给路由组件,而query属性又正好是一个对象,所以我们可以直接把queryreturn出去。

router/index.ts

{
  path: 'news1',
  name: 'newsName1',
  component: () => import('@/views/route1/news1/index.vue'),
  props(route) {
    return route.query
  }
},

route1/news1/index.vue

<template>
  <div>中文:{{ cnName }}</div>
  <div>英文:{{ enName }}</div>
  <div>年龄:{{ age }}</div>
</template>

<script lang="ts" setup>
defineProps(["cnName", "enName", "age"]);
</script>

1750164559134.png

此时页面上也可以正常展示了,所以用函数写法,就可以把query传的参数或者其他路由信息作为props传给路由组件。

3.对象写法

对象写法就像我们刚才讲函数写法的时候一样,把这个对象里的属性作为props传给路由组件,只是不接收路由信息参数。所以对象写法相当于阉割版的函数写法,不怎么用。

router/index.ts

{
  path: 'news1',
  name: 'newsName1',
  component: () => import('@/views/route1/news1/index.vue'),
  props: {
    a: 100,
    b: 200,
    c: 300
  }
},

路由的replace属性

路由跳转的时候会操纵浏览器的历史记录,你跳转过到哪些页面浏览器的历史记录上面都会有。路由操作浏览器的历史记录有两个动作,一个叫做push,另外一个叫做replace

push是什么意思呢,浏览器有一个栈专门存放浏览器的历史记录,每当我们浏览一个新页面的时候,都会往栈的最上面存放一个页面地址,旧的地址会在新地址的下面,就相当于有一个小指针,一直指着栈的最上面的页面地址,这就是push模式,一个一个把跳转过的页面推入到栈里,栈里会存放跳转过的所有历史记录,所以浏览器那个-><-就可以操控页面前进还是后退,就像是调整指针的位置。

1750247061885.png

replace不是一个一个往上堆,它是直接用新的页面去覆盖旧的页面,这个时候浏览器那个-><-就无法后退找到原本的旧页面,就更不可能往前进,因为栈里就只有当前页面一条历史记录,指针只能指向这个位置,这就是replace模式。

1750247347733.png

默认的模式是push,但是我们可以给改成replace,操作很简单,就是在RouterLink组件上添加replace属性就好了。

<template>
  <div class="screen-view">
    <!-- 嵌套的导航区 -->
    <div class="left-navmenu">
      <RouterLink
        replace
        :to="{ name: 'newsName1', query: { enName: 'Moon', cnName: '月亮', age: 18 } }"
        active-class="activeClass"
        >嵌套路由1</RouterLink
      >
      <RouterLink replace to="/route1/news2" active-class="activeClass">嵌套路由2</RouterLink>
      <RouterLink replace to="/route1/news3" active-class="activeClass">嵌套路由3</RouterLink>
    </div>
    <!-- 嵌套的展示区 -->
    <div class="right-container">
      <RouterView></RouterView>
    </div>
  </div>
</template>

编程式路由导航

我们到现在为止,所有的导航区都是通过RouterLink写的,但是RouterLink是一个组件,浏览器根本不认识它,所以最终它会解析成一个HTML标签中的a标签。

1750248140998.png

那如果我现在不想通过RouterLink去跳转路由该怎么办呢,比如我们点击某个图片然后跳转到相关页面,或者说执行完一个方法以后跳转到某个页面,再或者说我想3s后自动跳转页面,这该怎么做呢,上面的操作肯定是在js或者ts里面通过方法进行操作的,比如点击事件或者监视器,那RouterLink总不能写在ts里面吧。

而这种不通过RouterLink组件或者说RouterLink形成的a标签进行页面跳转的方法就叫做编程式路由导航。那这种方法该怎么实现呢。

我们前面说了route是路由,存放着某个路由的信息,我们通过这个获取过url上的参数,而router是路由器,上面存放着整个路由器的相关信息和操作,所以我们可以通过router进行页面跳转。routeuseRoute函数的执行返回,那么router自然就是useRouter函数的执行返回了,我们来打印一下看看有什么东西。

route1/index.vue

<template>
  <div class="screen-view">
    <!-- 嵌套的导航区 -->
    <div class="left-navmenu">
      <RouterLink
        replace
        :to="{ name: 'newsName1', query: { enName: 'Moon', cnName: '月亮', age: 18 } }"
        active-class="activeClass"
        >嵌套路由1</RouterLink
      >
      <RouterLink replace to="/route1/news2" active-class="activeClass">嵌套路由2</RouterLink>
      <RouterLink replace to="/route1/news3" active-class="activeClass">嵌套路由3</RouterLink>
    </div>
    <!-- 嵌套的展示区 -->
    <div class="right-container">
      <RouterView></RouterView>
    </div>
  </div>
</template>

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

let router = useRouter();
console.log(router);
</script>

1750249183333.png

其中有一个push方法和一个replace方法,需要注意一下,这两个方法就是进行跳转页面的,而这两个方法的不同之处就是模式不一样,对应着我们前面说的浏览器历史记录上的push模式和replace模式。

这两个方法怎么用呢?这里我举个例子,假如刚进入route1页面3s以后就自动跳转页面到news1,就可以直接使用push方法或者replace方法,然后携带一个参数,就是news1的路由路径。

route1/index.vue

<template>
  <div class="screen-view">
    <!-- 嵌套的导航区 -->
    <div class="left-navmenu">
      <RouterLink
        replace
        :to="{ name: 'newsName1', query: { enName: 'Moon', cnName: '月亮', age: 18 } }"
        active-class="activeClass"
        >嵌套路由1</RouterLink
      >
      <RouterLink replace to="/route1/news2" active-class="activeClass">嵌套路由2</RouterLink>
      <RouterLink replace to="/route1/news3" active-class="activeClass">嵌套路由3</RouterLink>
    </div>
    <!-- 嵌套的展示区 -->
    <div class="right-container">
      <RouterView></RouterView>
    </div>
  </div>
</template>

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

let router = useRouter();

setTimeout(() => {
  router.push('/route1/news1')
}, 3000);
</script>

push方法或者replace方法是用来跳转路由的,而RouterLink组件中to也是跳转路由的,所以push方法或者replace方法的参数,写法和RouterLink组件中to的写法是一致的,可以直接写字符串,也可以写对象。

// 字符串写法
setTimeout(() => {
  router.push("/route1/news1");
}, 3000);

// 对象写法
setTimeout(() => {
  router.push({
    path: "/route1/news1",
    query: {
      a: 1,
      b: 2,
    },
  });
}, 3000);

编程式路由导航通过router可以进行很多操作,比如它的back方法,就是回退到上一个页面,go方法是前进到前面的页面,还有其他的这里就不一一赘述了。

路由的重定向

页面刚打开的时候路由的路径就是/,像下面这样。

1750679708222.png

这个时候一般来说是不会展示任何东西的,为什么我们这里展示了是因为这只是一个测试案例,一般情况下刚进入项目的时候是不会展示任何东西的,此时体验就不会很好。

而路由如果设置了重定向,当页面的url上面的路径是该路由的路径的时候,就会给它重新跳转到别的页面上面去,所以此时我们就可以给/设置一个重定向,让其刚进入页面的时候就跳转到别的页面去,就不会有空白。

router/index.ts

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: '/',
      redirect: '/route1'  // 重定向
    },
    {
      path: '/route1',
      name: "routeName1",
      component: () => import("@/views/route1/index.vue"),
      children: [
        {
          path: 'news1',
          name: 'newsName1',
          component: () => import('@/views/route1/news1/index.vue'),
          props(route) {
            return route.query
          }
        },
        {
          path: 'news2',
          name: 'newsName2',
          component: () => import('@/views/route1/news2/index.vue')
        },
        {
          path: 'news3',
          name: 'newsName3',
          component: () => import('@/views/route1/news3/index.vue')
        },
      ]
    },
    {
      path: '/route2',
      name: "routeName2",
      component: () => import("@/views/route2/index.vue"),
    },
    {
      path: '/route3',
      name: "routeName3",
      component: () => import("@/views/route3/index.vue"),
    },
  ]
})

此时刚打开项目的时候url上的路径是/,因为给该路由设置了重定向(/ 其实也是一个路由),所以就会自动跳转到重定向指定的页面。

这个功能项目很常用,一般来说都是用于刚进入页面的时候跳转到登录页的。