简介
Vue Router 是 Vue 官方指定路由,其赋能 Vue 实现 单页应用(SPA,Single Page Application) 前端路由功能。
本文主要介绍下 Vue Router 4.x 的相关使用方法。
基本使用
下面通过一个小例子驱动阐述如何在 Vue3 下使用 Vue Router 4.x。
例子:假设当前页面有两个标签:/home
和/me
,要求点击不同的标签分别显示不同的组件页面。
思路:使用 Vue Router 配置相关路由,点击标签时,跳转到对应路由视图。具体操作步骤如下:
-
创建项目:首先创建一个示例项目:
# 此处使用 vite 进行构建 $ npm init vite@latest vue3_demos --template vue $ cd vue3_demos # 安装相关依赖 $ npm install # 启动应用 $ npm run dev
注:Vue3 安装更多方法,可参考:Vue3 安装
-
依赖引入:导入 Vue Router 依赖库:
$ npm install vue-router@4
-
创建组件:分别创键
Home.vue
和Me.vue
两个组件:<!-- file: components/Home.vue --> <template> <h1>Home Component</h1> </template> <style scoped> h1 { background-color: green; } </style> <!-- file: components/Me.vue --> <template> <h1>Me Component</h1> </template> <style scoped> h1 { background-color: yellow; } </style>
-
创建并配置路由对象:新建
router/index.js
,在此创建并配置路由对象:// file: router/index.js // 导入相关路由组件对象 import Home from '../components/Home.vue'; import Me from '../components/Me.vue'; // 定义路由映射:路由映射到具体组件 const routes = [ // 根路径 / 重定向到 /home { path: '/', redirect: '/home', }, // 前端路由 /home 对应组件 Home { path: '/home', component: Home, }, // 前端路由 /me 对应组件 Me { path: '/me', component: Me, }, ]; // 导入相关函数 import { createRouter, createWebHashHistory } from 'vue-router'; // 创建路由实例(`router`)并传递路由映射配置(`route`) const router = createRouter({ // 配置导航模式,此处采用 hash 模式 history: createWebHashHistory(), routes, }); // 导出 router 实例 export default router;
-
装载 Router 实例:创建全局
Vue
实例,并装载已配置的 Vue Router 实例:// file: main.js import { createApp } from 'vue'; import App from './App.vue'; import router from './router/index.js'; const app = createApp(App); // 装载 Vue Router 实例,确保整个 Vue 应用全局支持路由 app.use(router); app.mount('#app');
-
主页面配置路由导航:主页面通过
<router-link>
可配置路由导航,匹配的组件会最终被渲染到<router-view>
中:<!-- file: App.vue --> <template> <h1>Main Page</h1> <div class="nav"> <!-- router-link 最终会被渲染为一个 a 标签 --> <router-link to="/home">Home</router-link> <router-link to="/me">Me</router-link> </div> <!-- 路由出口:匹配组件最终被渲染位置 --> <router-view /> </template> <style scoped> .nav { width: 100px; display: flex; justify-content: space-around; } </style>
以上,就是一个简单的路由导航示例,其效果如下所示:
简单路由示例
功能介绍
下面会对 Vue Router 4.x 提供的一些常见功能进行简介。
路由对象
Vue Router 中存在两个最主要的路由对象为:
-
Router
:表示 Vue Router 实例对象。在 Vue Router 4.x 中,使用的是
createRouter()
函数创建Router
实例:import { createRouter, createWebHashHistory } from 'vue-router'; const routes = [...]; // 创建路由实例(`router`)并传递路由映射配置(`route`) const router = createRouter({ history: createWebHashHistory(), routes, }); export default router;
Router
主要是提供了对历史记录栈的操作功能,比如Router#push
方法可以往历史堆栈中推入一个新的 URL,Router#replace
方法可用于替换当前的 URL,还有Router#forward
、Router#back
、Router#go
...注:当代码中调用
createApp().use(Router)
时,其实就向 Vue 实例全局中注入了一个Router
实例,代码中获取该Router
实例的方法有如下几种:-
Options API:选项式 API 可通过
this.$routers
获取全局路由实例 -
Composition API:组合式 API 可通过函数
useRouter()
获取全局路由实例 -
template:模板中可通过
$router
获取全局路由实例
注:创建
Router
时,可设置一个激活样式linkActiveClass
,这样在主页选中<router-link>
时,对应标签就会被添加上自定义激活样式:// file: router/index.js const router = createRouter({ history: createWebHashHistory(), routes, // 设置标签激活时,添加样式类为 activeLink linkActiveClass: 'activeLink', }); <!-- file: App.vue --> <template> <h1>Main Page</h1> <!-- 点击选中标签时,自动添加 activeLink 类名 --> <router-link to="/home">Home</router-link> <router-link to="/me">Me</router-link> <router-view /> </template> <style scoped> /* 设置选中样式 */ .activeLink { background-color: red; } </style>
-
Options API:选项式 API 可通过
-
RouteLocationNormalized
:表示当前路由记录实例。routes
中配置的每条路由映射,我们称之为「路由记录」,其类型就是RouteLocationNormalized
:const routes = [ { path: '/home', component: () => import('@/components/Home.vue'), }, { path: '/me', component: Me, }, { path: '/user/:id*', component: () => import('@/components/User.vue'), }, ];
当在主页上点击选中相应路由标签时,就会跳转到相应路由映射组件,此时可以通过
Router#currentRoute
得到当前路由记录。比如,点击跳转到/user
时,Router#currentRoute
就可以获取/user
路由相关配置信息。其实还有其他更方便的方法获取到当前路由地址实例,主要包含如下:
-
Options API:对于选项式 API,可通过
this.$route
获取当前路由地址实例 -
Composition API:对于组合式 API,可通过
useRoute()
函数获取当前路由地址实例 -
template:模板中可通过
$route
获取当前路由地址实例
RouteLocationNormalized
提供了丰富的路由配置选项,这里列举一些比较常用的:-
hash
:string
类型,表示当前路由hash
部分。总是以#
开头,如果 URL 中没有hash
,则为空字符串。 -
path
:string
类型,表示当前路由路径,形如/user/1
-
fullpath
:string
类型,表示当前路由完整路径,包含path
、query
和hash
部分, -
name
:类型为RouteRecordName | null | undefined
,表示当前路由名称。注:建议为每个路由对象命名,方便后续编程导航。名称命名需唯一。
const routes = [ { name: 'user', // 路由命名 path: '/user/:id', component: () => import('@/components/User.vue'), }, ];
-
redirectedFrom
:类型为RouteLocation | undefined
,表示触发重定向的路由地址。 -
params
:类型为RouteParams
,用于获取路径参数。比如对于/user/:id
,$route.params
获取到的就是id
对应的信息。 -
query
:类型为LocationQuery
,表示 URL 查询参数。形如/user?id=1
,则query
对应的就是{id: 1}
-
meta
:类型为RouteMeta
,表示对当前路由的元数据,即额外信息描述。const routes = [ { meta: { name: 'Whyn' }, // 路由元数据 path: '/user/:id', component: () => import('@/components/User.vue'), }, ];
-
Options API:对于选项式 API,可通过
历史记录模式
前端路由的改变,其核心是不会向服务器发出请求,Vue Router 提供了两种模式支持该功能:
-
Hash 模式:Hash 模式 URL 构成中带有一个哈希字符
#
,更改#
字符后面的路径不会发送请求给服务器,其底层是基于浏览器的windows.onhashchange
事件。Hash 模式的优点是编程简单,缺点是路径不够简洁(多了额外字符
#
),且对 SEO 不够友好。Vue Router 中通过
createWebHashHistory()
函数配置 Hash 模式:import { createRouter, createWebHashHistory } from 'vue-router' const router = createRouter({ history: createWebHashHistory(), routes: [ //... ], })
-
History 模式:History 模式利用了 HTML5 History Interface 中新增的
pushState()
和replaceState()
等方法,实现了浏览器的历史记录栈。调用这些方法修改 URL 历史记录时,不会触发浏览器向后端发送请求。History 模式的优点是路径简洁且控制更灵活,缺点是 History 模式下,用户在浏览器中直接访问或手动刷新时,会触发真正的请求,服务器没有当前请求资源时,会返回一个
404
错误。解决的方法也很简单,就是后端添加一个回退路由,在 URL 匹配不到任何静态资源时,默认返回前端页面的index.html
。比如,Nginx 中可以配置如下:location / { try_files $uri $uri/ /index.html; }
Vue Router 中通过
createWebHistory()
函数配置启动 History 模式:import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(), routes: [ //... ], })
最后,Vue Router 更推荐使用 History 模式。
路由懒加载
前端每个路由都会对应一个组件,前面我们使用的方式都是导入相应组件,然后配置映射到对应路由中:
const Home = { template: '<div>Home</div>' }
const routes = [
{ path: '/', component: Home },
]
当路由比较多时,会导致加载的组件也变多,这样在应用打包后,生成的 JavaScript 包会臃肿变大,影响页面加载效率。
因此,Vue Router 提供了 路由懒加载 功能,其将不同路由对应的组件分割成不同的代码块,然后当路由被访问时,才动态加载对应组件,这样效率就会更高。
Vue Router 支持开箱即用的动态导入,如下所示:
// 直接加载
import Home from '@/components/Home.vue';
// 懒加载
const Me = () => import('@/components/Me.vue')
const routes = [
{ path: '/home', component: Home, }, // 直接加载
{ path: '/me', component: Me }, // 懒加载
];
注:上述代码使用@
代表src
目录,使能需要进行如下配置:
// file: vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
const path = require('path');
export default defineConfig({
plugins: [vue()],
resolve: {
// 别名配置
alias: {
'@': path.resolve(__dirname, './src'),
},
},
});
此时如果执行构建:
$ npm run build
dist/index.html 0.48 KiB
dist/assets/index.4ada91f0.js 2.13 KiB / gzip: 1.13 KiB
dist/assets/Me.a2557100.js 0.23 KiB / gzip: 0.20 KiB # Me.vue
dist/assets/index.d8be49df.css 0.12 KiB / gzip: 0.12 KiB
dist/assets/Me.0aae35d5.css 0.04 KiB / gzip: 0.06 KiB # Me.vue
dist/assets/vendor.fd7d0278.js 71.78 KiB / gzip: 28.44 KiB
可以看到,配置懒加载组件Me.vue
会被单独打包到一个.js
文件中。
实际上,Vue Router 中,懒加载基本原理是:component
或components
配置接收的是一个返回Promise
组件的函数,因此,我们也可以进行自定义动态导入,其实就是创建一个返回Promise
的函数,该Promise
返回一个组件,比如:
const UserDetails = () =>
Promise.resolve({
/* 组件定义 */
})
动态路由
一个很常见的场景,比如,根据用户id
获取用户信息,通常对应的 RESTFul API 为/user/{id}
,即匹配/user/1
、/user/2
...
Vue Router 将这种形式的 URL 称之为 动态路由,其使用:
进行使能,形式如下所示:
const User = {
template: '<div>User</div>',
}
// 这些都会传递给 `createRouter`
const routes = [
// 动态段以冒号开始
{ path: '/user/:id', component: User },
]
此时,上述path
可以匹配/user/1
、/user/username
等等。
动态路由也支持正则匹配,可以设置更加精细匹配规则,常见匹配设置如下:
const routes = [
// /:id -> 仅匹配数字
{ path: '/:id(\\d+)' },
// /:username -> 匹配其他任何内容
{ path: '/:username' },
// /:chapters -> 匹配 /one, /one/two, /one/two/three, 等
{ path: '/:chapters+' },
// /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
{ path: '/:chapters*' },
// 匹配 /users 和 /users/posva
{ path: '/users/:userId?' },
// 匹配 /, /1, /1/2, 等
{ path: '/:chapters(\\d+)*' },
]
动态路由信息可以通过$route.params
进行获取,一个示例代码如下:
配置路径/user/:id(\d+)
映射到组件User.vue
,并展示id
具体值:
<!-- file: components/User.vue -->
<template>
<!-- 模板获取 route.params 属性 -->
<h1>User Component: {{ $route.params }}</h1>
</template>
<script>
import { onBeforeRouteUpdate } from 'vue-router';
export default {
name: 'User',
setup(props, context) {
// Router 钩子函数
onBeforeRouteUpdate((to, from, next) => {
// 代码获取 route.params 属性
console.log(to.params.id);
next();
});
},
};
</script>
// file: router/index.js
const routes = [
{
path: '/user/:id(\\d+)',
component: () => import('@/components/User.vue'),
},
];
<!-- file: App.vue -->
<template>
<h1>Main Page</h1>
<div class="nav">
<router-link :to="'/user/' + id" @click="randomId">User</router-link>
</div>
<router-view />
</template>
<script >
import { ref } from '@vue/reactivity';
export default {
name: 'App',
setup(props, context) {
const id = ref(1);
function randomInRange(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
const randomId = () => (id.value = randomInRange(0, 100));
return {
id,
randomId,
};
},
};
</script>
注:动态路由如果使用可变参数进行修饰,则:
-
/user/:id*
:*
表示匹配 0 个或多个,此时的$params.id
为数组形式,即{id: []}
-
/user/:id+
:+
表示匹配 1 个或多个,此时的$params.id
为数组形式,即{id: []}
-
/user/:id?
:?
表示匹配 0 个或 1 个,此时的$params.id
为值,即{id: 10}
注:由于动态路由实际上映射的是同一个组件,因此,在进行动态路由切换时,会复用该组件实例,所以组件生命周期钩子不会被调用,如果想监听动态路由改变,需要手动watch
当前路由this.$route.params
上对应的属性,或者使用导航守卫钩子函数,比如onBeforeRouteUpdate
进行监听。
嵌套路由
- 嵌套路由:就是一个路由内部可以嵌套一些子路由。
比如,/user
是一个路由,/user/one
、/user/two
是/user
下的两个子路由,所以/user
是一个嵌套了/user/one
、/user/two
的嵌套路由。
再简单进行理解,一个路由对应一个组件,因此,嵌套路由其实就是一个父组件内部包含了多个子组件,且这些子组件也是通过路由进行访问。
嵌套路由的配置很简单,只需为父路由设置children
属性即可,下面以一个例子进行驱动,阐述嵌套路由。
示例:假设现在有一个新闻版块,该版块内部含有两个子版块,分别为财经版块和体育版块,用代码进行实现。
分析:父路由对应组件news.vue
,其内嵌套两个子路由/news/finance
和/news/sports
,分别对应两个组件Finance.vue
和Sports.vue
。
嵌套路由搭建步骤如下:
-
首先创建所有对应组件:
<!-- file: components/nested_router/Finance.vue --> <template> <h3>Finance Component</h3> </template> <!-- file: components/nested_router/Sports.vue --> <template> <h3>Sports Component</h3> </template> <!-- file: components/nested_router/News.vue --> <template> <h1>News Component</h1> <div class="nav"> <router-link to="/news/finance">Finance</router-link> <router-link to="/news/sports">Sports</router-link> </div> <router-view /> </template> <style scoped> /* router-link 最终会被转换为 a 标签 */ .nav a { margin-left: 10px; } </style>
News.vue
由于是父组件,因此其内部包含router-view
标签用于展示嵌套子路由页面组件。 -
配置路由信息:
// file: ./router/nested.js import { createRouter, createWebHistory } from 'vue-router'; const routes = [ { path: '/news', component: () => import('@/components/nested_router/News.vue'), // 配置嵌套路由 children: [ { // /news 重定向到 /news/finance path: '/news', redirect: '/news/finance', }, { // /news/finace path: 'finance', component: () => import('@/components/nested_router/Finance.vue'), }, { // /news/sports path: 'sports', component: () => import('@/components/nested_router/Sports.vue'), }, ], }, ]; export default createRouter({ // 使用 History 模式 history: createWebHistory(), routes, });
-
主页面添加展示
/news
映射的组件New.vue
:// file: main.js import { createApp } from 'vue'; import App from './App.vue'; import router from './router/nested.js'; const app = createApp(App); app.use(router); app.mount('#app'); <!-- file: App.vue --> <template> <h1>Main Page</h1> <router-link to="/news">News</router-link> <!-- 路由出口:匹配组件最终被渲染位置 --> <router-view /> </template>
编程式导航
前面我们都是通过<router-link>
标签实现导航链接功能,实际上当我们点击<router-link>
时,其内部会调用Router#push
方法实现真正的路由导航,因此,我们也可以直接通过编程方式,即调用Router
相关方式,手动实现导航跳转。
Vue Router 主要提供了以下几个方法供我们实现路由跳转:
-
Router#push
:该方法会向历史堆栈添加一个新记录,可供我们导航到指定的 URL。Router#push
是<router-link>
底层默认调用的导航方法:声明式 编程式 <router-link :to="...">
router.push(...)
Router#push
支持多种参数类型,其常见调用方式如下所示:// 字符串路径 router.push('/users/eduardo') // 带有路径的对象 router.push({ path: '/users/eduardo' }) // 命名的路由,并加上参数,让路由建立 url router.push({ name: 'user', params: { username: 'eduardo' } }) // 带查询参数,结果是 /register?plan=private router.push({ path: '/register', query: { plan: 'private' } }) // 带 hash,结果是 /about#team router.push({ path: '/about', hash: '#team' })
注:
<router-link>
中的to
属性与Router#push
方法接收的参数类型一致,所以上述配置也适用于to
属性。注:如果同时提供了
path
和params
,则params
会被忽略。建议使用name
属性替换path
,避免潜在问题:router.push({ name: 'Me' });
-
Router#replace
:该方法同样支持路由导航,但是与Router#push
不同的是,该方法不会向历史堆栈中添加导航记录,而是直接替换当前导航记录。
一般只有当明确禁止跳转回前一个路由时,才会使用该方法。<router-link>
可通过添加replace
属性,使能Router#replace
:声明式 编程式 <router-link :to="..." replace>
router.replace(...)
Router#push
也可以实现replace
功能,只需为路由添加replace: true
属性:router.push({ path: '/home', replace: true }) // 相当于 router.replace({ path: '/home' })
-
Router#go
:该方法可以横跨跳转历史堆栈:// 向前移动一条记录,与 router.forward() 相同 router.go(1) // 返回一条记录,与router.back() 相同 router.go(-1) // 前进 3 条记录 router.go(3) // 如果没有那么多记录,静默失败 router.go(-100) router.go(100)
下面还是通过一个示例驱动进行讲解。
例子:比如主页有两个按钮,要求点击两个按钮显示Home.vue
和Me.vue
两个页面。
思路:/home
路由映射组件Home.vue
,/me
路由映射组件Me.vue
,然后为按钮添加点击事件,通过调用Router
相关方式实现路由跳转。
具体步骤如下:
-
创建路由页面
Home.vue
和Me.vue
。内容参考上文 -
配置路由信息:
// file: router/index.js import { createRouter, createWebHistory } from 'vue-router'; export default createRouter({ history: createWebHistory(), routes: [ { name: 'Home', path: '/home', component: () => import('@/components/Home.vue'), }, { name: 'Me', path: '/me', component: () => import('@/components/Me.vue'), }, ], });
-
加载 Vue Router 并配置主页面:
// file: main.js import { createApp } from 'vue'; import App from './App.vue'; import router from './router/index.js'; const app = createApp(App); // 装载 Vue Router 实例,确保整个 Vue 应用全局支持路由 app.use(router); app.mount('#app'); <!-- file: App.vue --> <template> <h1>Main Page</h1> <div class="nav"> <button @click="nav2Home">Home</button> <button @click="nav2Me">Me</button> </div> <router-view /> </template> <script setup> import { useRouter } from "vue-router" const router = useRouter(); const nav2Home = () => router.push('/home'); const nav2Me = () => router.push('/me'); </script> <style scoped> .nav button { margin-left: 10px; } </style>
运行结果如下图所示:
programmatic_navigation_demo
命名路由
前面很多处都提到了 命名路由,其实就是为路由配置一个name
属性:
const routes = [
{
name: 'user', # 命名路由
path: '/user/:username',
component: User
}
]
使用命名路由,除了避免了手动硬编码 URL 外,最大的好处就是它会对params
属性自动进行编码/解码。
具体使用命名路由时,只需为<router-link>
的to
属性指定一个命名对象即可:
<router-link :to="{ name: 'user', params: { username: 'erina' }}"> User </router-link>
命名视图
一个<router-view>
只能显示一个组件页面,即使是嵌套路由,同一时刻也只是显示一个组件页面。但是如果一个路由需要同时显示两个及以上组件页面,此时就需要同时提供多个<router-view>
,并且为<router-view>
设置相应名称,路由配置时会指定相应组件显示到对应名称的<router-view>
上,这种具备名称的的<router-view>
称之为 命名视图。
只需为<router-view>
设置name
属性,即为 命名视图:
<router-view name="sidebar" />
注:实际上,所有的<router-view>
都是命名视图,未配置name
属性时,其默认名为default
。
举个例子:比如现在主页上有两个组件页面:导航栏sidebar
和主区域main
,同时呈现在主页上,要求使用命名视图完成。
思路:主页需要配置两个命名视图的<router-view>
,然后路由配置时,指定相应组件显示到对应的命名视图上即可。
具体步骤如下:
-
创建导航栏组件和主区域组件:
<!-- components/named_view/SideBar.vue --> <template> <nav> <u> <li>Nav 1</li> <li>Nav 2</li> </u> </nav> </template> <style scoped> nav { background-color: green; } </style> <!-- components/named_view/Main.vue --> <template> <p>Main Content</p> </template> <style scoped> p { background-color: yellow; } </style>
-
配置路由信息:根路由需要配置两个子组件,并指定各自要显示到的命名视图:
// file: router/index.js import { createRouter, createWebHistory } from 'vue-router'; export default createRouter({ history: createWebHistory(), routes: [ { path: '/', // 主页面同时显示多个路由映射组件 components: { // Main.vue 显示到 default 视图上 default: () => import('@/components/named_view/Main.vue'), // SideBar.vue 显示到 sidebar 视图上 sidebar: () => import('@/components/named_view/SideBar.vue'), }, }, ], });
-
主页配置两个命名视图,分别渲染对应的组件:
// file: main.js 参考上文 <!-- file: App.vue --> <template> <h1>Main Page</h1> <!-- 点击跳转到根路由 --> <router-link to="/">Main</router-link> <router-view name="sidebar" /> <router-view /> </template>
效果如下:
Named View Demo
重定向
- 重定向:就是当访问一个路由时,会被拦截并重新导航到另一个路由中。
重定向只需通过配置$route
即可,Vue Router 大致提供如下几种类型重定向配置:
-
path
:直接配置重定向路径:// 将 /home 重定向到 / const routes = [{ path: '/home', redirect: '/' }]
-
命名路由:命名路由本质是一个路由,因此也可直接命名路由,最终重定向到该命名路由对应的
path
:// 将 /home 重定向到命名路由 homepage const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
-
函数:可以将
redirect
设置会一个函数,动态返回重定向地址信息:const routes = [ { path: '/a', redirect: (to) => { // 参数 to 是源路由地址实例对象 const { hash, params, query } = to; if (query.to === 'search') { // 返回一个路由映射配置信息 return { path: '/search', query: { q: params.searchText }}; } if (params.id) { // 返回动态路由 return '/with-params/:id'; } else { // 返回路由地址 return '/bar'; } }, }, ];
别名
- 别名:Vue Router 中,别名 指的是一个路由对应两个路径,即访问这两个路径都能跳转到该路由。
比如对于下面的配置:
const routes = [
{
path: '/user_one', // 路径
alias: '/user_1', // 别名
component: User,
},
];
其实就是为/user_one
设置了一个别名/user_1
,此时访问/user_one
或/user_1
都可以导航到User.vue
组件。
注:如果想同时指定多个别名,则需进行如下配置:
const routes = [
{
path: '/user_one',
component: User,
alias: ['/user_1', '/user_yi'], // 使用数组即可
},
];
路由组件传参
- 路由组件传参:其实就是跳转时,给路由映射组件传递参数。
前面内容,在模板中,我们都是通过$route
直接获取路由信息:
<!-- file: components/User.vue -->
<template>
<h1>User Component: {{ $route.params.name }}</h1>
</template>
// file: router/index.js
const routes = [
{
path: '/user/:name',
component: () => import('@/components/User.vue'),
},
];
这种做法其实紧耦合了路由与组件,对组件复用性产生消极影响。
一个更灵活的解决办法是通过为组件动态传递参数,Vue Router 大致提供了如下几种路由参数传递方法:
-
布尔模式:为路由地址映射配置一个
props: true
,此时route.params
会被传递给组件的props
:比如,上面的例子使用参数传递,可修改为如下:
// file: router/index.js const routes = [ { path: '/user/:name', component: () => import('@/components/User.vue'), props: true, // 使能路由参数传递 }, ]; <!-- file: components/User.vue --> <template> <!-- 显示传递的参数 --> <h1>User Component: {{ name }}</h1> </template> <script> export default { name: 'User', props: { name: String, // 接收传递的参数 }, }; </script>
-
命名视图:对于有命名视图的路由,必须显示为每个命名视图定义
props
配置:const routes = [ { path: '/user/:name', components: { default: User, sidebar: Sidebar }, // default 视图使能参数传递,sidebar 视图关闭参数传递 props: { default: true, sidebar: false } } ]
-
对象模式:将
props
设置为对象,直接传递该对象给组件:const routes = [ { path: '/user/:name', components: { default: User, sidebar: Sidebar }, // 直接传递对象 props: { name: 'Whyn' } } ]
注:对象模式此时直接传递
props
对象,与路由params
没有关系。当我们需要传递静态内容给到路由组件时,对象模式就很合适。 -
函数模式:可以将
props
设置为一个函数,该函数参数是路由地址信息$route
,因此可以在函数内部提取当前路由相关信息,结合自己一些静态内容,组合进行设置,更加灵活:const routes = [ { path: '/user', component: () => import('@/components/User.vue'), props: (route) => ({ query: route.query.name }), }, ];
上述配置中,比如此时我们跳转到
/user?name=Whyn
时,则会传递{ query: 'Whyn' }
给到User.vue
组件:<!-- file: components/User.vue --> <template> <h1>User Component: {{ query }}</h1> </template> <script> export default { name: 'User', props: { query: String, }, }; </script>
导航守卫
- 导航守卫:所谓 导航守卫,其实就是一些路由钩子,在进行路由跳转或取消时,会触发相应钩子,我们可以在这些钩子中,检测路由跳转及获取相关数据,植入我们自己的操作。
Vue Router 总共提供了如下三种类型导航守卫:
-
全局守卫:可以监控所有路由跳转的导航守卫。具体可在细分为如下三种类型:
-
全局前置守卫:当触发一个导航跳转时,全局前置守卫会被触发调用(多个全局前置守卫会按照创建顺序依次回调)。
可通过
Router#beforeEach
注册一个全局前置守卫:const router = createRouter({ ... }) router.beforeEach((to, from, next) => { // ... })
全局前置守卫是异步解析执行的,即回调函数与
async
和Promise
工作方式一样:// 上述代码相当于如下 router.beforeEach(async (to, from, next) => { // canUserAccess() 返回 `true` 或 `false` return await canUserAccess(to) })
全局前置守卫每个守卫方法可接受三个参数:
-
to
:表示即将要进入的路由目标 -
from
:当前导航正要离开的路由对象 -
next
:可选参数,用于触发并传递额外数据给下一个路由对象:
router.beforeEach((to, from, next) => { // 进行管道的下一个钩子 next(); // 中断当前导航,URL 重置为 from 路由路径 next(false); // 跳转到指定路由 /home next('/home'); // 同 next('/home') next( {path: '/home'} ); // 如果 next 参数是一个 Error 实例,则导航会被终止, // 且参数 error 会被传递给 Router#onError() 回调 next(error); })
全局前置守卫每个守卫方法返回值有如下几种可选:
-
true
|undefined
:返回true
或undefined
,表示导航有效,并自动调用下一个导航守卫:router.beforeEach((to, from) => { // 没有 return,则表示 return undefined return false; });
注:如果显示声明了第三个参数
next
,则必须手动调用next
进行跳转:router.beforeEach((to, from, next) => { // false next(true); // 或者 undefined next(); });
-
false
:表示取消导航跳转,即当前 URL 重置到from
路由地址router.beforeEach((to, from) => { // 取消导航,停留在 from 路由页面 return false; });
-
一个路由地址:指定导航跳转地址,相当于调用了
Router#push
:const routes = [ // ... { path: '/user', component: () => import('@/components/User.vue') }, ], router.beforeEach((to, from) => { if (to.path === '/user') { return true; return '/user'; });
-
Error
:抛出异常,此时会取消导航并回调Router#onError
全局错误钩子:router.beforeEach((to, from, next) => { // 手动抛异常,会被 onError 捕获到 throw 'error occured!!'; }); router.onError((error, to, from) => { console.log(error, to, from); });
-
-
全局解析守卫:全局解析守卫与全局前置守卫类似,都是在 每次导航 时都会被触发,但是全局解析守卫会确保在 所有组件内守卫和异步路由组件被解析后,才会被正确调用。
可通过
Router#beforeResolve
方法注册一个全局解析守卫:router.beforeResolve((to, from, next) => { console.log('Router beforeResolve'); });
Router#beforeResolve
是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。 -
全局后置钩子:全局后置钩子是在导航被确认后,才进行调用,因此,该钩子不会携带
next
函数,也不会改变导航状态。可通过
Router#afterEach
注册一个全局后置钩子:router.afterEach((to, from, failure) => { console.log('Router afterEach'); });
Router#afterEach
对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。
-
-
路由独享守卫:如果只关注特定路由导航跳转事件,那么只需为相应路由添加导航守卫即可。
只需在路由配置上定义
beforeEnter
函数即可注册一个路由独享导航守卫:const routes = [ { path: '/home', component: () => import('@/components/Home.vue'), // 路由独享守卫 beforeEnter: (to, from, next) => { console.log('Home: Route beforeEnter'); next(); }, // 支持传递多个钩子函数 // beforeEnter: [(..)=>{..}, (..)=>{..}], }, ];
beforeEnter
只在进入路由时触发,更改params
、query
或hash
时,不会触发beforeEnter
。 -
组件内守卫:一个路由最终映射为一个组件,因此我们也可以在组件内定义导航守卫,捕获路由跳转导航相关信息。
组件内守卫在 Options API 中可通过为组件添加
beforeRouteEnter
、beforeRouteUpdate
和beforeRouteLeave
函数进行注册:const UserDetails = { template: `...`, beforeRouteEnter(to, from) { // 在渲染该组件的对应路由被验证前调用 // 不能获取组件实例 `this` ! // 因为当守卫执行时,组件实例还没被创建! }, beforeRouteUpdate(to, from) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候, // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this` }, beforeRouteLeave(to, from) { // 在导航离开渲染该组件的对应路由时调用 // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this` }, }
组件内守卫在 Composition API 中可通过在
setup
函数中定义onBeforeRouteUpdate
和onBeforeRouteLeave
分别注册 update 和 leave 守卫:<script> import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'; export default { name: 'Home', setup(props, context) { console.log('setup = onBeforeRouteEnter'); // update 守卫确认导航跳转,因此没有 next 参数 onBeforeRouteUpdate((fto, from) => { console.log('onBeforeRouteUpdate'); }); // leave 守卫同样没有 next 参数 onBeforeRouteLeave((to, from) => { console.log('onBeforeRouteLeave'); }); }, }; </script>
Composition API 中,
setup
等于beforeRouteEnter
,在路由进入时被触发;onBeforeRouteUpdate
在第一次路由进入时,不会被触发,只有在该路由重复进入,组件被复用时才会被触发(比如params
的改变);onBeforeRouteLeave
在离开当前路由时会被触发。
最后,全局导航守卫、路由独享守卫和组件内守卫完整的导航解析流程如下图所示:
注:流程图来源于网上,侵删。
路由导航完整解析流程附录
- 本篇博文 Demo 源码可查看:vue-router4.x-demo
网友评论