介绍
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/123456
,那么route.params.id
的值就是123456
。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
的页面。
属性
to
:指定要导航到的目标路由路径。有两种写法:- 字符串写法:
to="/about"
, - 对象写法:
:to="{ path: '/about' }
,可以是一个包含路径、查询参数、命名路由等信息的对象。
- 字符串写法:
replace
:控制导航行为,决定是否在历史记录中替换当前条目而不是添加新条目。- 默认值是
false
。 - 如果设置为
true
,导航将不会留下历史记录,相当于使用history.replaceState
而不是history.pushState
。
- 默认值是
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
。
在选项式API中使用编程式导航
在 Vue3 中,选项式API可以使用 this.$router.push()
、this.$router.replace()
和 this.$router.go()
等方法进行编程式导航。
-
this.$router.push()
:- 类似于在浏览器中点击链接,会向历史记录中添加一个新的条目。
- 接受一个字符串路径作为参数,或者一个包含路径信息的对象。例如:
this.$router.push('/')
,导航到/
路径。
this.$router.push({ path: '/about', query: { id: 1 } })
,导航到/about
路径,并带有查询参数id=1
。
-
this.$router.replace()
:- 与
push()
类似,但不会向历史记录中添加新条目,而是替换当前的历史记录条目。
- 与
-
this.$router.go()
:- 用于在历史记录中前进或后退。接受一个整数作为参数(正数- 前进,0 - 刷新,负数 - 后退)。例如:
this.$router.go(-1)
后退一步。
- 用于在历史记录中前进或后退。接受一个整数作为参数(正数- 前进,0 - 刷新,负数 - 后退)。例如:
-
this.$router.back()
:- 用于在历史记录中后退到上一个页面。
-
this.$router.forward()
:- 用于在历史记录中前进到下一个页面。
在组合式API中使用编程式导航
在 Vue 3 中,组合式 API 使用 useRouter
和 useRoute
进行编程式导航。
<script lang="ts" setup>
import { useRouter, useRoute } from 'vue-router';
const router = useRouter();
const route = useRoute();
// 编程式导航
const navigateToHome = () => {router.push('/about');
};
</script>
useRouter
返回一个路由实例,可以使用其方法进行编程式导航,如 router.push()
、router.replace()
等。相当于在模板中使用 $router
。
useRoute
返回当前的路由地址,可以获取路径、参数、查询参数等。相当于在模板中使用 $route
。
带参数的动态路由匹配
可以使用带参数的动态路由匹配来实现根据不同的参数加载不同的页面内容。
在路由配置中定义动态路由参数:
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', component: News},
];const router = createRouter({history: createWebHistory(),routes,
});export default router;
在这个例子中,/news/:newsId
是一个动态路由,其中 :newsId
是一个参数占位符。
现在,像 /news/123456
和 /news/765432
这样的 URL 都会映射到同一个路由。
路径参数 用冒号 :
表示。当一个路由被匹配时,它的 params
的值将在每个组件中以 route.params
的形式暴露出来。
在组件中使用 useRoute
获取路由参数:
<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
参数进行数据获取和页面渲染。
可以在同一个路由中设置有多个 路径参数,它们会映射到 route.params
上的相应字段。
- 匹配模式
/news/:id
- 匹配路径
/news/123456
route.params
值为:{ newsId: '123456' }
- 匹配路径
- 匹配模式
/news/:newsId/isShare/:shareFlag
- 匹配路径
/news/123456/isShare/true
route.params
值为:{ newsId: '123456', shareFlag: 'true' }
- 匹配路径
响应路由参数的变化
在上面的例子中,当用户从/news/123456
导航到 /news/765432
时,相同的组件实例将被重复使用。
因为两个路由都渲染同个组件,组件被复用,组件的生命周期钩子不会被调用。
由于生命周期钩子不被调用,不能依赖传统的钩子来进行数据获取。可以在 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>
路由的匹配语法
- 静态路由
直接指定一个固定的路径,例如/about
表示当 URL 为根路径加上/about
时,匹配该路由。
{ path: '/about', component: About}
- 动态路径参数
在路径中使用冒号:
后跟参数名来定义动态路径参数。例如/news/:newsId
,当 URL 为/news/123456
时,其中123456
会被作为参数newsId
的值。
{ path: '/news/:newsId', component: News}
- 使用
?
表示可选参数
在参数后面加上?
表示该参数是可选的。例如/news/:newsId?
,URL 可以是/news
或者/news/123456
。
注意:用?
标记的参数不能重复。
{ 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/123456
:
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/123456
:
嵌套路由
嵌套路由允许在一个父路由下定义多个子路由,每个子路由对应不同的组件,从而实现页面的分层结构。当访问父路由时,可以通过在父路由组件中渲染<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>
路由的props配置
在 Vue Router 中,通过设置props
属性,可以将路由参数自动传递给组件的 props,使组件不直接依赖于$route
对象,避免在组件内部通过$route.params
来获取参数。
props: true
会将路由参数作为 props 传递给组件。
const routes = [{ path: '/news/:newsId', component: News, props: true, },
];
在选项式API中,会将路由参数作为 props
传递给组件。
例如,当访问/news/123
时,组件将接收一个名为newsId
的 prop,其值为123
。
<script lang="ts">
export default {props: ['newsId'],
}
</script>
newsId
具有响应性,跟随路由参数的改变而改变。
在组合式API中,会将路由参数作为attrs
传递给组件,通过useAttrs()
访问attrs
。
当访问/news/123
时,组件将接收一个名为newsId
的 attr,其值为123
。
<script setup lang="ts">
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传给组件。
在选项式API 、组合式API中的获取方式同上。
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',};},},
];
- props的对象写法
也可以直接设置为一个对象,将静态的 props 传递给组件。这些 props 不会随着路由参数的变化而变化。
在选项式API 、组合式API中的获取方式同上。
const routes = [{path: '/news/:newsId',component: News,props: {defaultName: 'Static Name',},},
];
重定向
路由重定向是一种将用户从一个路由导航到另一个路由的方式。
重定向也是通过 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 进行动态路由匹配。