介绍
Vue是一个典型的SPA单页面应用框架。SPA单页面是指网站只有一个html
页面,所有的页面切换都只在这个html
页面中完成。不同组件的切换全部交由路由来完成。
Vue Router 是 Vue.js 官方的客户端路由解决方案。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。
客户端路由的作用是在单页应用 (SPA) 中将浏览器的 URL 和用户看到的内容绑定起来。当用户在应用中浏览不同页面时,URL 会随之更新,但页面不需要从服务器重新加载。
Vue Router 基于 Vue 的组件系统构建,通过配置路由来告诉 Vue Router 为每个 URL 路径显示哪些组件。
安装 Vue Router
使用npm
直接安装:
npm install vue-router@4
如果启动一个新项目,可以使用 create-vue
脚手架工具创建一个基于 Vite 的项目,并包含加入 Vue Router 的选项
npm create vue@latest
路由器与路由
在 Vue Router 中,“路由器(router)” 和 “路由(route)” 有特定的含义和作用。
路由器(router)
- Vue Router 中的路由器是一个管理应用路由的对象。它负责协调和控制整个应用的导航过程。
- 类似于网络中的物理路由器,它决定了用户在应用中的不同页面之间如何进行导航。
- 路由器负责定义应用中的所有路由。通过配置路由对象数组,指定不同路径对应的组件。
- 配置属性:
-
history
:必需。定义路由的历史模式(可以理解为路由器的工作模式)。取值:createWebHistory()
: 使用浏览器的历史记录,适合部署在服务器上的应用。URL没有哈希符号(#
)。createMemoryHistory()
:在内存中管理历史记录,不依赖于浏览器的历史记录或 URL。适合在非浏览器环境中使用,如 Node.js等createWebHashHistory()
:哈希模式。使用这种历史模式时,URL 中会包含一个哈希符号(#
)。
当页面中的路由发生变化时,浏览器不会向服务器发送请求来获取新的页面内容。Vue Router 通过监听浏览器地址栏中哈希部分的变化来触发路由的切换。
-
routes
:必需。路由规则数组,定义了应用中的各个路由路径与对应的组件。 -
sensitive
:可选。控制路由匹配是否区分大小写。- 取值类型:布尔值,默认值为
false
(路由匹配不区分大小写)。 - 当设置为
true
时,路由匹配区分大小写。例如,/About
和/about
将被视为不同的路由路径。
- 取值类型:布尔值,默认值为
-
strict
:可选。控制带有结尾斜杠的路径和不带结尾斜杠的路径是否被视为不同的路由。- 取值类型:布尔值,默认值为
false
(Vue Router 会自动添加或移除结尾斜杠以匹配路由)。 - 当设置为
true
时,带有结尾斜杠的路径和不带结尾斜杠的路径将被视为不同的路由。例如,/about
和/about/
将被视为不同的路由路径。
- 取值类型:布尔值,默认值为
-
路由(route)
- 代表应用中的一个特定的路由状态。它包含了当前路由的信息,如路径、参数、查询参数、组件等。
path
:当前路由的路径。例如,如果当前页面的 URL 是/about
,那么route.path
的值就是/about
。params
:路由参数对象。当路由路径中包含参数时,可以通过这个对象获取参数的值。例如,对于路径/user/:id
,如果当前 URL 是/user/123
,那么route.params.id
的值就是123
。query
:查询参数对象。用于获取 URL 中的查询参数。比如/search?q=vue
,可以通过route.query.q
获取查询参数q
的值。name
:命名路由,具有唯一性。可以通过路由的名称进行导航。matched
:一个数组,包含当前路由匹配到的所有路由记录。可以用于获取当前路由的父路由信息等。hash
:URL 中的哈希值部分,如果有哈希路由或者锚点,这里会存储对应的哈希字符串。例如,对于URL/about#section1
,route.hash
的值是'#section1'
。fullPath
:完成解析后的 URL,包含查询参数和哈希值。例如,对于 URL/search?q=vue&page=2#result
,route.fullPath
的值是/search?q=vue&page=2#result
。
在 Vue Router 中,路由器是管理整个应用路由的核心对象,而路由则代表了特定的路由状态和信息。
一个路由(route)就是一组映射关系(key-value),多个路由需要路由器(router)进行管理。
在前端路由中,key是路径,value是组件,将路径与组件一一对应起来,这种对应关系就路由。
路由器与路由共同协作,实现了 Vue 应用的页面导航和路由管理功能。
基础使用
创建路由实例:
// router/index.ts
// 创建一个路由器,并暴露出去
// 第一步:引入createRouter, createWebHistory
import { createRouter, createWebHistory } from 'vue-router';
// 引入要呈现的组件
import Home from '@/views/Home.vue';
import About from '@/views/About.vue';
// 定义路由配置
// path 属性定义了路由的路径。 component 属性指定了与该路由对应的组件。
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
];
// 第二步:创建路由器
const router = createRouter({
history: createWebHistory(), // 路由器的工作模式
routes,
});
export default router;
在 main.ts
中,引入路由模块并将其传递给 Vue 应用实例。
// 引入 createApp 用于创建实例
import { createApp } from 'vue'
// 引入 App.vue 根组件
import App from './App.vue'
// 引入路由器
import router from './router'
// 创建一个应用
const app = createApp(App)
// 使用路由器
app.use(router)
// 挂载整个应用到app容器中
app.mount('#app')
app.use(router)
实现以下功能:
- 全局注册
RouterView
和RouterLink
组件。 - 添加全局
$router
和$route
属性。 - 启用
useRouter()
和useRoute()
组合式函数。 - 触发路由器解析初始路由。
在模板中使用 <router-link>
组件来创建链接进行页面导航。
<template>
<div class="layout-wrap">
<header class="layout-header-box">vue路由测试</header>
<div class="layout-bottom-box">
<aside class="layout-aside-box">
<router-link to="/">首页</router-link>
<router-link to="/about">关于我们</router-link>
</aside>
<section class="layout-section-box">
<router-view></router-view>
</section>
</div>
</div>
</template>
这段代码通过 Vue Router 的 <router-link>
和 <router-view>
组件实现了页面的路由导航和内容切换功能,为构建多页面应用提供了基础布局。
<aside class="layout-aside-box">
:侧边栏区域,其中包含了2个<router-link>
组件,分别用于导航到不同的路由页面。<router-link to="/">首页</router-link>
:点击这个链接会触发导航到路径为/
的页面。<router-link to="/about">关于</router-link>
:点击导航到/about
页面。
<section class="layout-section-box">
:主要内容区域,用于显示通过路由切换进来的不同页面内容,由<router-view>
组件占位,当导航到不同路由时,对应的组件内容会在这里显示。
当路由被匹配时,对应的组件会被渲染在 <router-view>
所在的位置。
当用户访问根路径时,Home
组件会被渲染;当访问 /about
路径时,About
组件会被渲染。
通过Vue Devtools查看组件:
查看路由:
现在活跃的路由是/
,Home
组件被渲染。
路由导航
在 Vue 中,路由导航主要通过 <router-link>
和编程式导航方法来实现。
<router-link>
<router-link>
是一个 Vue 组件,用于在模板中创建链接以进行页面导航。
使用组件 <router-link>
创建链接,Vue Router 能够在不重新加载页面的情况下改变 URL,处理 URL 的生成、编码和其他功能。
例如:<router-link to="/about">关于我们</router-link>
,当用户点击这个链接时,会导航到路径为 /about
的页面。
<router-link>
的属性
to
:指定要导航到的目标路由路径。有两种写法:- 字符串写法:
to="/about"
, - 对象写法:
:to="{ path: '/about' }
,可以是一个包含路径、查询参数、命名路由等信息的对象。
- 字符串写法:
replace
:控制路由跳转时操作浏览器历史记录的模式。- 默认值是
false
。 - 如果设置为
true
,导航到一个新的 URL,同时替换当前的历史记录。 - 示例:
<router-link replace to="/news">News</router-link>
- 默认值是
active-class
:指定当链接处于激活状态时应用的 CSS 类名。默认是"router-link-active"
。exact
:精确匹配模式,用于控制激活状态的判断方式。- 默认值为
false
。 - 当设置为
true
时,只有当链接的路径完全匹配当前路由路径时,才会应用激活状态的类名。 - 例如:
<router-link :to="/about" exact>关于页面</router-link>
,如果当前路径是/about
,则该链接会处于激活状态;如果当前路径是/about/contact
,则该链接不会处于激活状态。
- 默认值为
append
:在相对路径的导航中,控制是否将目标路径附加到当前路径后面。- 默认值为
false
。 - 当设置为
true
时,相对路径的导航会将目标路径附加到当前路径后面。
- 默认值为
<!-- 由于是绝对路径导航,无论当前在哪个页面,都会直接导航到/contact路径。 -->
<router-link to="/contact">绝对路径导航</router-link>
<!-- 不设置append或设置为false(默认值) -->
<router-link to="../about" append>相对路径导航(不附加)</router-link>
<!-- 设置append为true-->
<router-link to="../about" append="true">相对路径导航(附加)</router-link>
假设当前页面的路径为 /parent/child
。
<router-link to="../about" append="false">相对路径导航(不附加)</router-link>
,点击这个链接后导航的路径为/parent/about
。
相对路径../about
表示从当前路径向上一级(即从child
回到parent
),然后再找about
路径。不附加时,只考虑相对路径本身。<router-link to="../about" append="true">相对路径导航(附加)</router-link>
,点击这个链接后导航的路径为/parent/child/about
。
相对路径../about
表示从当前路径向上一级(即从child
回到parent
),然后再找about
路径。附加时,会将相对路径附加到当前路径后面,所以是/parent/child/about
。
使用 <router-link>
创建 a
标签来定义导航链接
编程式导航
除了使用 <router-link>
创建 a
标签来定义导航链接,还可以使用 router 的实例方法,通过编写代码来实现。
在组件内部,使用 $router
属性访问路由。如果使用组合式 API,调用 useRouter()
来访问路由器。
-
选项式API可以使用
this.$router
、this.$route
访问路由器和当前路由。this.$router
用于访问全局的路由器实例。它允许你在组件中执行导航操作,如跳转到其他路由、获取路由信息等。this.$route
用于获取当前路由对象的信息。它提供了关于当前活动路由的各种信息,如路径、参数、查询参数等。
-
在组合式API中,因为在
setup
里面没有访问this
,所以不能直接访问this.$router
或this.$route
。使用useRouter
和useRoute
函数作为替代。- 注意:在模板中,仍然可以访问
$router
和$route
。如果只在模板中使用这些对象的话,是不需要useRouter
或useRoute
的。 - 示例:
- 注意:在模板中,仍然可以访问
<script lang="ts" setup>
import { useRouter, useRoute } from 'vue-router';
const router = useRouter();
const route = useRoute();
// 编程式导航,实现路由跳转到/about
const navigateToHome = () => {
router.push('/about');
};
</script>
注意: 下面的示例中的 router
指代路由器实例。
router主要功能和方法
router.push()
:- 用于导航到不同的路由。会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,会回到之前的 URL。
- 接受一个字符串路径作为参数,或者一个包含路径、查询参数、路由参数等信息的对象。
- 示例:
// 字符串路径
router.push('/news')
// 带有路径的对象
router.push({ path: '/news' })
// 带查询参数,结果是 /news?newsId=123
// 注意:如果提供了 path,params 会被忽略
router.push({ path: '/news', query: { newsId: 123 } })
// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'news', params: { newsId: 123 } })
// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })
-
router.replace()
:- 用于导航到一个新的 URL,同时替换当前的历史记录(不会向历史记录中添加新记录)。
-
router.go()
:- 用于在历史记录中前进或后退。接受一个整数作为参数(正数- 前进,0 - 刷新,负数 - 后退)。
- 示例:
this.$router.go(-1)
后退一步。
-
router.back()
:- 用于在历史记录中后退到上一个页面。
-
router.forward()
:- 用于在历史记录中前进到下一个页面。
当点击 <router-link>
时,内部会调用router.push
方法,所以点击 <router-link :to="...">
相当于调用 router.push(...)
。
由于属性 to
与 router.push
接受的对象种类相同,所以两者的规则完全相同。
路由传参
路由传参有多种方式: 通过查询字符串传递参数(query
)、通过路由路径传递参数(params
)、通过 路由的props配置 传参。
通过查询字符串传递参数(query
)
传递参数
- 使用
router.push
传递参数:
// 假设已经创建了 Vue Router 实例 router
router.push({ path: '/news', query: { newsId: 123 } });
// 也可以传递一个函数作为第二个参数,在函数中返回包含query参数的对象。
// 这个函数接收当前的路由对象作为参数,可以根据当前路由的状态来动态地设置query参数。
router.push({ path: '/news' }, (to) => {
return { query: { newsId: 123,...to.query } };
});
- 使用
<router-link>
:
在 HTML 中的<router-link>
组件上设置to
属性时,可以直接在对象中包含query
参数。
<!-- 跳转并携带query参数(to的字符串写法) -->
<router-link to="/news/detail?newsId=123">跳转</router-link>
<!-- 跳转并携带query参数(to的对象写法) -->
<router-link
:to="{
//name:'news', //用name也可以跳转
path:'/news',
query:{
newsId: 123
}
}"
>
跳转
</router-link>
在路由路径中看到 ?
时,意味着从这个位置开始后面的部分是查询参数。
?newsId=123
部分就是查询参数。
在组件中获取参数
- 选项式API 通过
$route
直接访问query
参数:
<template>
<div>新闻ID: {{ $route.query.newsId }}</div>
</template>
<script>
export default {
data() {
return {};
},
};
</script>
- 组合式API 通过
useRoute
函数获取当前路由对象,然后从该对象中访问query
参数:
<template>
<div>新闻ID: {{ newsId }}</div>
</template>
<script setup>
import { useRoute } from 'vue-router';
const route = useRoute();
const newsId = route.query.newsId ;
</script>
query注意事项
query
参数是一种松散类型的数据传递方式,可以传递字符串、数字、布尔值、对象、数组等各种类型的数据。query
参数会在 URL 中显示,用户可以直接看到和修改(如果应用允许)。query
参数在页面刷新后仍然保留,除非手动清除或者通过编程方式改变路由。
通过路由路径传递参数(params
)
定义路由时指定参数占位符
import { createRouter, createWebHistory } from 'vue-router';
import Home from '@/views/Home.vue';
import News from "@/views/News.vue";
const routes = [
{ path: '/', component: Home },
{ path: '/news/:newsId', name: 'news', component: News},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
这里的 :newsId
是路由参数占位符。当访问 /news/123
时,123
会被作为参数传递给 News.vue
。
传递参数
- 使用
router.push
传递参数:
// 假设已经创建了 Vue Router 实例 router
// 传递参数进行导航
router.push({ name: 'news', params: { newsId: 123 } });
// 使用路径形式传递参数
router.push({ path: '/news/123' });
});
- 使用
<router-link>
:
<!-- 跳转并携带params参数(to的字符串写法) -->
<router-link :to="/news/123">跳转</router-link>
<!-- 跳转并携带params参数(to的对象写法) -->
<router-link
:to="{
name:'news', //用name跳转
params:{
newsId: 123,
}
}"
>
跳转
</router-link>
在组件中获取参数
- 选项式API 通过
$route
直接访问params
参数:
<template>
<div>新闻ID: {{ $route.params.newsId }}</div>
</template>
<script>
export default {
data() {
return {};
},
};
</script>
- 组合式API 通过
useRoute
函数获取当前路由对象,然后从该对象中访问params
参数:
<template>
<div>新闻ID: {{ newsId }}</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute();
// 获取路由参数
const newsId = computed(() => route.params.newsId);
// 使用newsId do something
</script>
在这个组件中,通过 useRoute
获取当前路由信息,然后使用计算属性 newsId
来获取动态路由参数 newsId
的值。
可以根据获取到的 newsId
参数进行数据获取和页面渲染。
params注意事项
- 传递
params
参数时,若使用to
的对象写法,必须使用name
配置项,不能用path
。 - 传递
params
参数时,需要提前在规则中占位。 - 当指定
params
时,可提供string
或number
参数。任何其他类型(如对象、布尔等)都将被自动字符串化。对于可选参数,你可以提供一个空字符串(""
)或null
来移除它。
通过 路由的props配置 传参
在 Vue Router 中,通过设置props
属性,可以将路由参数自动传递给组件的 props,使组件不直接依赖于$route
对象,避免在组件内部通过$route.params
来获取参数。
props: true
将路由收到的所有 params
参数作为 props 传给路由组件
const routes = [
{ path: '/news/:newsId', component: News, props: true, },
];
例如,当访问/news/123
时,组件将接收一个名为newsId
的 prop,其值为123
。
- 在选项式API中,使用
props
属性接收参数:
<script lang="ts">
export default {
props: ['newsId'],
}
</script>
- 在组合式API中,使用
defineProps()
接收参数:
<script setup lang="ts">
defineProps(['newsId'])
</script>
newsId
具有响应性,跟随路由参数的改变而改变。
- 在组合式API中,如果不
defineProps()
接收参数,则会将路由参数作为attrs
传递给组件,通过useAttrs()
访问attrs
。
当访问/news/123
时,组件将接收一个名为newsId
的 attr,其值为123
。
<script setup lang="ts">
import { toRefs, useAttrs } from 'vue';
const attrs = useAttrs();
const { newsId, name } = toRefs(attrs);
// 也可以使用computed属性
// import { computed, useAttrs } from 'vue';
// const attrs = useAttrs();
// 动态获取attrs,使attrs具有响应性
// const computedAttrs = computed(() => attrs);
// 动态获取newsId,使newsId具有响应性
// const newsId = computed(() => computedAttrs.value.newsId);
</script>
newsId
具有响应性,跟随路由参数的改变而改变。
props: function
可以设置为一个函数,该函数接收route
对象作为参数,并返回一个对象。把返回的对象中每一组key-value作为props传给组件。
- 可以自己决定将什么作为props给路由组件:
interface CustomRouteParams {
id?: string;
}
const routes = [
{
path: '/news/:newsId',
component: News,
props: (route): { id: number; name: string } => {
const { id } = route.params as CustomRouteParams;
return {
id: id ? parseInt(id) : 0,
name: 'Default Name',
};
},
},
];
- 也可以直接把
query
参数作为 props 传给 路由组件:
const routes = [
{
path: '/news/:newsId',
component: News,
props: (route) => {
return route.query;
},
},
];
参数接收方式同props: true
。
props的对象写法
也可以直接设置为一个对象,将静态的 props 传递给组件。这些 props 不会随着路由参数的变化而变化。
const routes = [
{
path: '/news/:newsId',
component: News,
props: {
defaultName: 'Static Name',
},
},
];
参数接收方式同props: true
。
带参数的动态路由匹配
很多时候,我们需要将给定匹配模式的路由映射到同一个组件。例如,我们可能有一个 News 组件,它应该对所有新闻进行渲染,但新闻 ID 不同。在 Vue Router 中,可以在路径中使用一个动态字段来实现,这个动态字段被称为 路径参数 。
路径参数 用冒号 :
表示。当一个路由被匹配时,它的 params
的值将在每个组件中以 route.params
的形式暴露出来。
const routes = [
{ path: '/news/:newsId', name: 'news', component: News},
];
/news/:newsId
是一个动态路由,其中 :newsId
是一个参数占位符。
现在,像 /news/123
和 /news/765
这样的 URL 都会映射到同一个路由。
当访问/news/:newsId
时,不同的 newsId
值会动态地匹配到这个路由,并且在组件中可以通过 $route.params.newsId
(选项式 API)或 useRoute().params.newsId
(组合式 API)来获取这个参数值。
可以在同一个路由中设置有多个 路径参数,它们会映射到 route.params
上的相应字段。
- 匹配模式
/news/:id
- 匹配路径
/news/123
route.params
值为:{ newsId: '123' }
- 匹配路径
- 匹配模式
/news/:newsId/isShare/:shareFlag
- 匹配路径
/news/123/isShare/true
route.params
值为:{ newsId: '123', shareFlag: 'true' }
- 匹配路径
响应路由参数的变化
在上面的例子中,当用户从/news/123
导航到 /news/765
时,相同的组件实例将被重复使用。
因为两个路由都渲染同个组件,组件被复用,组件的生命周期钩子不会被调用。
由于生命周期钩子不被调用,不能依赖传统的钩子来进行数据获取。可以在 setup
函数中使用 watch
监听路由参数的变化(route.params
的任意属性),当参数变化时触发数据获取操作:
<script setup lang="ts">
import { watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
watch(() => route.params.newsId, (newId, oldId) => {
// 对路由变化做出响应...
})
</script>
动态路由匹配 与 query
传参的区别
例如: /news/123/标题
与 /news?newsId=123&title=标题
- URL 结构不同:动态路由匹配的参数是路由路径的一部分,而查询字符串的参数是在 URL 末尾通过
?
和&
连接的键值对。 - 获取参数的方式不同:在组件中,动态路由的参数通过
params
获取,而查询字符串的参数通过query
获取。 - 使用场景不同:
- 动态路由匹配通常用于根据不同的参数值加载不同的资源或展示不同的内容,参数值与路由的逻辑关系更为紧密。
- 例如,一个新闻应用中,
/news/:newsId
可以根据不同的新闻 ID 加载不同的新闻详情页面。
- 例如,一个新闻应用中,
- 查询字符串则更适合传递一些额外的、不太影响路由逻辑的参数,或者用于临时的筛选、排序等操作。
- 比如在新闻列表页面,可以通过查询字符串传递搜索关键词、排序方式等参数来筛选新闻列表。
- 动态路由匹配通常用于根据不同的参数值加载不同的资源或展示不同的内容,参数值与路由的逻辑关系更为紧密。
路由的匹配语法
- 静态路由
直接指定一个固定的路径,例如/about
表示当 URL 为根路径加上/about
时,匹配该路由。
{ path: '/about', component: About}
- 动态路径参数
在路径中使用冒号:
后跟参数名来定义动态路径参数。例如/news/:newsId
,当 URL 为/news/123
时,其中123
会被作为参数newsId
的值。
{ path: '/news/:newsId', component: News}
- 使用
?
表示可选参数
在参数后面加上?
表示该参数是可选的。例如/news/:newsId?
,URL 可以是/news
或者/news/123
。
注意:用?
标记的参数不能重复。
{ path: '/news/:newsId?', component: News}
- 使用星号
*
表示通配符
*
可以匹配任何路径。例如/:catchAll(.*)
,这个路由会匹配所有未被其他路由匹配的路径。
注意:用*
标记的路由参数可以重复。
{ path: '/:catchAll(.*)', component: NotFound }
/:catchAll(.*)
这个路由会匹配所有未被其他路由匹配的路径,并渲染 NotFound
组件。
- 使用括号和正则表达式
可以在参数的括号中使用正则表达式来更精确地控制参数的匹配规则。
{ path: '/user-:afterUser(.*)', component: UserGenericComponent }
这个路由规则匹配以 /user-
开头的路径。例如 /user-profile
、/user-settings
等。
:afterUser(.*)
是一个正则表达式捕获组,会将 /user-
后面的部分存储在 route.params.afterUser
中。
还可以写其它的正则:
// /:orderId -> 仅匹配数字
{ path: '/:orderId(\\d+)' }
// 正则表达式[a-zA-Z0-9]+ 限制参数 username 只能由字母和数字组成
{ path: '/user/:username([a-zA-Z0-9]+)' }
注意:
在使用正则表达式时,确保转义反斜杠( \
),就像 \d
(变成\\d
),在 JavaScript 中实际传递字符串中的反斜杠字符。
捕获所有路由或 404 Not found 路由
// 第一种写法:
{ path: '/:catchAll(.*)', component: NotFound }
// 第二种写法:
{ path: '/:pathMatch(.*)*', component: NotFound },
path: '/:catchAll(.*)'
与 path: '/:pathMatch(.*)*'
的区别:
path: '/:catchAll(.*)'
:
这个表达式表示匹配所有路径,并将未匹配的部分存储在一个名为catchAll
的参数中。
它通常用于捕获所有未被其他特定路由匹配的路径,以显示 404 页面或进行通用的错误处理。
示例:
const routes = [
{ path: '/', component: Home, },
{ path: '/news/:newsId', component: News, },
{ path: '/about', component: About, },
{ path: '/:catchAll(.*)', component: NotFound },
]
在浏览器访问路由中没有的/user/123
:
path: '/:pathMatch(.*)*'
::pathMatch(.*)*
使用了正则表达式捕获组,会将未匹配的路径部分存储在route.params.pathMatch
中。pathMatch
用于捕获未匹配的路径部分。- 最后的
*
表示可以匹配零个或多个路径片段。
示例:
const routes = [
{ path: '/', component: Home, },
{ path: '/news/:newsId', component: News, },
{ path: '/about', component: About, },
{ path: '/:pathMatch(.*)*', component: NotFound },
]
在浏览器访问路由中没有的/user/123
:
嵌套路由
嵌套路由允许在一个父路由下定义多个子路由,每个子路由对应不同的组件,从而实现页面的分层结构。当访问父路由时,可以通过在父路由组件中渲染<router-view>
来显示子路由的内容。
在 Vue Router 的路由配置中,为父路由设置children
属性来定义子路由:
import { createRouter, createWebHistory } from 'vue-router';
import Home from './views/Home.vue';
import ParentComponent from './views/ParentComponent.vue';
import ChildComponent from './views/ChildComponent.vue';
const routes = [
{ path: '/', component: Home },
{
path: '/parent',
component: ParentComponent,
children: [
{
path: 'child',
component: ChildComponent,
},
],
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
在这个例子中,/parent
是父路由,/parent/child
是子路由。
在父路由对应的组件(ParentComponent)中,放置一个<router-view>
标签来渲染子路由的内容。
<template>
<div>
<h2>Parent Component</h2>
<router-view></router-view>
</div>
</template>
<script>
export default {
//...
};
</script>
可以使用<router-link>
组件来导航到嵌套路由:
<router-link to="/parent/child">Go to Child Route</router-link>
路由守卫
在 Vue Router 中,路由守卫(Route Guards)用于控制路由的访问权限和导航流程。
路由守卫可以在路由切换的不同阶段执行特定的逻辑。
Vue Router 提供了以下几种类型的路由守卫: 全局路由守卫、路由独享守卫、组件内守卫。
全局路由守卫
全局前置守卫(beforeEach
)
- 触发时机:在导航触发前执行,无论任何路由的切换都会经过这个守卫。
- 使用方式:
router.beforeEach((to, from, next) => {... })
- 参数说明
to
:即将要进入的目标路由对象。from
:当前导航正要离开的路由对象。next
:是一个必须调用的函数,用于决定导航的流程。不同的调用方式有不同的效果:next()
:允许导航继续进行,即按照正常的路由切换流程进行下一步。如果在全局前置守卫(beforeEach
)中调用,将继续执行后续的守卫和目标路由的加载。next(false)
:中断当前的导航。如果在全局前置守卫或路由独享守卫中调用,会阻止导航到目标路由,并且不会触发后续的守卫和目标路由的加载。next('/login')
:将导航重定向到指定的路径。在全局前置守卫或路由独享守卫中调用时,会中断当前的导航,并将用户重定向到指定的路由。
示例:
import router from '@/router';
router.beforeEach((to, from, next) => {
// 逻辑判断
if (to.meta.requiresAuth &&!isAuthenticated()) {
// 如果目标路由需要认证且用户未登录,重定向到登录页面
next('/login');
} else {
next();
}
});
全局后置守卫(afterEach
)
- 触发时机:在导航完成后执行,无论导航成功还是被取消都会触发。
- 使用方式:
router.afterEach((to, from) => {... })
- 参数说明
to
:进入的目标路由对象。from
:离开的路由对象。
示例:
router.afterEach((to, from) => {
// 可以在这里记录页面访问日志等操作
console.log(`Navigated from ${from.path} to ${to.path}`);
});
路由独享守卫
- 定义位置:在单个路由的配置中定义。
- 触发时机:仅在进入该特定路由时执行。
示例:
const routes = [
{
path: '/dashboard',
component: DashboardComponent,
meta: { requiresAuth: true },
beforeEnter: (to, from, next) => {
// 只有当访问 /dashboard 这个路由时才会执行这个守卫逻辑
if (isAuthorizedForDashboard()) {
next();
} else {
next('/access-denied');
}
},
},
];
组件内守卫
在组件内部定义,可以更具体地控制组件在路由导航中的行为。
beforeRouteEnter
:
- 触发时机:在路由进入该组件前执行。
- 使用方式:
beforeRouteEnter(to, from, next) => {... }
- 特点:
- 此时组件实例还未创建,所以不能访问
this
。 - 可以通过传递一个回调函数给
next
来访问组件实例。比如next(vm => {... })
- 此时组件实例还未创建,所以不能访问
beforeRouteUpdate
:
- 触发时机:当路由参数变化但组件被复用时执行。
- 使用方式:
beforeRouteUpdate(to, from, next) => {... }
beforeRouteLeave
:
- 触发时机:在导航离开该组件前执行。
- 使用方式:
beforeRouteLeave(to, from, next) => {... }
在选项式API中使用:
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
const someData = ref(null);
return { someData };
},
beforeRouteEnter(to, from, next) {
// 可以在这里获取数据并在组件创建后使用
fetchData(to.params.id).then(data => {
next(vm => {
// 访问组件实例,例如设置初始数据
vm.someData = data;
});
});
},
beforeRouteUpdate(to, from, next) {
// 处理路由参数变化等情况
fetchData(to.params.id).then(data => {
someData.value = data;
next();
});
},
beforeRouteLeave(to, from, next) {
// 例如,提示用户是否保存未保存的更改
if (inputValue.value) {
const confirmLeave = window.confirm('You have unsaved changes. Are you sure you want to leave?');
if (confirmLeave) {
next();
} else {
next(false);
}
} else {
next();
}
},
};
</script>
在组合式 API(<script setup>
)中,Vue Router 将更新和离开守卫作为组合式 API 函数公开:
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'
// 与 beforeRouteLeave 相同,无法访问 `this`
onBeforeRouteLeave((to, from) => {
const answer = window.confirm(
'Do you really want to leave? you have unsaved changes!'
)
// 取消导航并停留在同一页面上
if (!answer) return false
})
const userData = ref()
// 与 beforeRouteUpdate 相同,无法访问 `this`
onBeforeRouteUpdate(async (to, from) => {
//仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
}
})
</script>
重定向
路由重定向是一种将用户从一个路由导航到另一个路由的方式。
重定向也是通过 routes
配置来完成,下面例子是从 /home
重定向到 /
:
const routes = [{ path: '/home', redirect: '/' }]
或者:
const routes = [{ path: '/home', redirect: { path: '/' } }]
重定向的目标也可以是一个命名的路由:
const routes = [{ path: '/home', redirect: { path: 'homepage' } }]
使用函数进行重定向,动态返回重定向目标:
const routes = [
{
path: '/search/:searchText',
redirect: to => {
// 方法接收目标路由作为参数
// return 重定向的字符串路径/路径对象
return { path: '/search', query: { q: to.params.searchText } }
},
},
{
path: '/search',
// ...
},
]
函数接收一个参数to
,代表目标路由对象,可以根据这个对象的属性或其他条件来决定重定向的目标路径。
导航守卫(如beforeEnter
)只应用在目标路由上。如果进行了路由重定向,导航守卫只会在最终的目标路由上生效,而不会在跳转的中间路由上生效。
在上面的例子中,在 /home
路由中添加 beforeEnter
守卫不会有任何效果。
在写 redirect
的时候,可以省略 component
配置,因为它从来没有被直接访问过,所以没有组件要渲染。唯一的例外是嵌套路由:如果一个路由记录有 children
和 redirect
属性,它也应该有 component
属性。
因为在嵌套路由的情况下,父路由需要有一个组件来作为子路由的容器。即使父路由主要用于重定向,它仍然需要一个组件来承载子路由的渲染。
嵌套路由的重定向:
const routes = [
{
path: '/parent',
redirect: '/parent/child',
component: ParentComponent,
children: [
{
path: 'child',
component: ChildComponent,
},
],
},
];
这个例子中,/parent
路由有重定向和子路由,所以需要有ParentComponent
作为组件,以便在用户被重定向到/parent/child
之前,能够有一个容器来渲染子路由的内容。
相对重定向
相对重定向根据当前路由的路径来动态地确定重定向的目标路径,而不是使用绝对路径进行重定向。
示例:
const routes = [
{
// 将总是把/users/123/posts重定向到/users/123/profile。
path: '/users/:id/posts',
redirect: to => {
// 该函数接收目标路由作为参数
// 相对位置不以`/`开头
// 或 { path: 'profile'}
return 'profile'
},
},
{
path: '/users/:id/profile',
component: UserProfile,
},
]
路由别名
路由别名(route alias)就是为一个路由定义一个或多个备用的路径名称,使得用户可以通过不同的 URL 访问相同的页面内容。
在 Vue Router 的路由配置中,使用alias
属性来定义路由别名:
const routes = [
{ path: '/about', component: About, alias: '/about-us' },
];
为/about
路由定义了一个别名/about-us
。用户可以通过/about
或/about-us
访问About
组件。
可以为一个路由定义多个别名,只需在alias
属性中提供一个数组:
const routes = [
{ path: '/about', component: About, alias: ['/about-us', '/about-page'] },
];
用户可以通过/about
、/about-us
或/about-page
访问About
组件。
动态路由也可以定义别名,别名中的动态参数部分应该与原始路由中的动态参数部分保持一致:
const routes = [
{ path: '/about/:id', component: About, alias: '/about-us/:id' },
];
/about/:id
和/about-us/:id
都将指向About
组件,并且可以根据不同的 ID 进行动态路由匹配。
路由懒加载
在 Vue 应用中,路由懒加载是一种优化技术,它可以在需要的时候才加载特定的路由组件,而不是在应用初始化时一次性加载所有的路由组件。这样可以显著提高应用的初始加载速度,特别是当应用有很多路由和大型组件时。
在 Vue Router 中使用动态导入(import ()
)实现路由懒加载:
// 将
// import About from '@/views/About.vue'
// 替换成
const About = () => import('@/views/About.vue')
const routes = [
{
path: '/about',
component: About ,
},
{
path: '/some-route',
component: () => import('./SomeComponent.vue'),
},
];
在这个例子中,About.vue
组件将在访问/about
路由时才被动态加载,SomeComponent.vue
组件将在访问/some-route
路由时才被动态加载。