Vue3-VueRouter
Vue Router是Vue.js官方的路由管理器。它和 Vue.js 的核⼼深度集成,让构建单⻚⾯应⽤变得易如反掌。包含的功能有:
- 嵌套的路由/视图表
- 模块化的、基于组件的路由配置
- 路由参数、查询、通配符
- 基于 Vue.js 过渡系统的视图过渡效果
- 细粒度的导航控制
- 带有⾃动激活的 CSS class 的链接
- HTML5 历史模式或 hash 模式,在 IE9 中⾃动降级
- ⾃定义的滚动条⾏为
起步
⽤ Vue.js + Vue Router 创建单⻚应⽤,是⾮常简单的。使⽤ Vue.js,我们已经可以通过组合组件来组成应⽤程序,当你要把 Vue Router添加进来,我们需要做的是,将组件 (components) 映射到路由(routes),然后告诉 Vue Router 在哪⾥渲染它们
安装
npm i vue-router -S
在main.js中
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter)
推荐使用:vue add router 添加插件(记得提前提交)
基本使用
router.js
import Vue from 'vue'
//1.导⼊
import Router from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'
//2.模块化机制 使⽤Router
Vue.use(Router)
//3.创建路由器对象
const router = new Router({
//mode: 'history', //history模式 干净的网页地址 没有#/之类的符号存在
routes:[{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}]
})
export default router;
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
// 4.挂载根实例
router,
render: h => h(App)
}).$mount('#app')
做好以上配置后
<template>
<div id="app">
<div id="nav">
<!-- 使⽤router-link组件来导航 -->
<!-- 通过传⼊to属性指定连接 -->
<!-- router-link默认会被渲染成⼀个a标签 -->
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<!-- 路由出⼝ -->
<!-- 路由匹配的组件将被渲染到这⾥ -->
<router-view/>
</div>
</div>
</template>
打开浏览器,切换Home和About超链接,查看效果
命名路由
在配置路由的时候,给路由添加名字,访问时就可以动态的根据名字来进行访问
const router = new Router({
routes:[{
path: '/home',
name: 'home',
component: Home
},
{
path: '/about',
name:'about'
component: About
}]
})
要链接到一个命名路由,可以给router-link
的to
属性传一个对象:
<router-link :to="{name:'home'}">Home</router-link> |
<router-link :to="{name:'about'}">About</router-link> |
动态路由匹配
我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有⼀个 User
组件,对于所有 ID 各不相同的⽤户,都要使⽤这个组件来渲染。那么,我们可以在 vue-router
的路由路径中使⽤“动态路径参数”(dynamic segment) 来达到这个效果
User.vue
<template>
<div>
<h3>⽤户⻚⾯</h3>
</div>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped></style>
路由配置
const router = new Router({
routes:[{
path: '/user/:id',
name: 'user',
component: User,
}]
})
javascript
复制代码<router-link :to="{name:'user',params{id:1}}">User</router-link> |
查看效果
当匹配到路由时,参数值会被设置到this.$route.params,可以在每个组件中使用,于是,我们可以更新User
的模板,输出当前用户的ID:
<template>
<div>
<h3>⽤户⻚⾯{{$route.params.id}}</h3>
</div>
</template>
响应路由参数的变化
提醒⼀下,当使⽤路由参数时,例如从 /user/1
导航到 /user/2
,原来的组件实例会被复⽤。因为两个路由都渲染同个组件,⽐起销毁再创建,复⽤则显得更加⾼效。不过,这也意味着组件的⽣命周期钩⼦不会再被调⽤。
复⽤组件时,想对路由参数的变化作出响应的话,你可以简单地watch (监测变化) $route
对象:
/*使⽤watch(监测变化) $route对象
watch: {
$route(to, from) {
console.log(to.params.id);
}
}, */
// 或者使⽤导航守卫
beforeRouteUpdate(to,from,next){
//查看路由的变化
//⼀定要调⽤next,不然就会阻塞路由的变化
next();
}
404路由
const router = new Router({
routes:[
//....
// 匹配不到路由时,404⻚⾯显示
{
path: '*',
component: () => import('@/views/404')
}]
})
当使⽤通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后。路由 { path: '*' }
通常⽤于客户端 404错误
当使⽤⼀个通配符时, $route.params
内会⾃动添加⼀个名为 pathMatch
参数。它包含了 URL 通过通配符被匹配的部分:
{
path: '/user-*',
component: () => import('@/views/User-admin.vue')
}
this.$route.params.pathMatch // 'admin'
匹配优先级
有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。
查询参数
类似像地址上出现的这种:http://localhost:8080/page?id=1&title=foo
const router = new Router({
routes:[
//...
{
path: '/page',
name: 'name',
component:() => ('@/views/Page.vue')
}]
})
javascript
复制代码<router-link :to="{name: 'page', query:{id: 1, title: 'foo'}}">User</router-link>
访问http://localhost:8080/page?id=1&title=foo查看Page
Page.vue
<template>
<div>
<h3>Page页面</h3>
<h3>{{$route.query.userId}}</h3>
</div>
</template>
<script>
export default {
created () {
//查看路由信息对象
console.log(this.$route);
},
}
</script>
<style>
</style>
路由重定向和别名
重定向
Example是从/
重定向到/home
:
const router = new Router({
mode: 'history',
routes: [
// 重定向
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'home',
component: Home
},
]
})
重定向的目标也可以是一个命名的路由:
const router = new VueRouter({
routes: [
{
path:'/',
redirect: {name: 'name'}
}
]
})
别名
{
path: 'user/:id',
name: 'user',
component: User,
alias: '/alias'
}
“别名”的功能让你可以自由地将UI结构映射到任意的URL,而不是受限于配置的嵌套路由结构。
路由组件传参
在组件中使⽤ $route
会使之与其对应路由形成⾼度耦合,从⽽使组件只能在某些特定的 URL 上使⽤,限制了其灵活性。
使⽤ props
将组件和路由解耦:
取代与$route的耦合
Index.js
{
path: '/user/:id',
name: 'user',
component: User,
//props: true
//props也可以是一个函数
props: (route) => {
id: route.params.id,
title: route.query.title
}
}
User.vue
<template>
<div>
<h3>⽤户⻚⾯{{$route.params.id}}</h3>
<h3>⽤户⻚⾯{{id}}</h3>
</div>
</template>
<script>
export default{
//....
props: {
id: {
type: String,
default: ''
},
},
}
</script>
props也可以是个函数
User.vue
<template>
<div>
<h3>⽤户⻚⾯{{id}}-{{title}}</h3>
</div>
</template>
<script>
export default {
// ...
props: {
id: {
type: String,
default: ''
},
title:{
type: String
}
},
};
</script>
编程式导航
除了使用<router-link>
创建a标签来定义导航链接,我们还可以借助router的实例方法。通过编写代码来实现。
注意:在Vue实例内部,你可以通过router访问路由实例。因此你可以调用this.router访问路由实例。因此你可以调用this.router访问路由实例。因此你可以调用this.router.push。
声明式 | 编程式 |
---|---|
<router-link :to="..."> |
router.push(...) |
该⽅法的参数可以是⼀个字符串路径,或者⼀个描述地址的对象。例如:
//字符串
this.$router.push('home')
//对象
this.$router.push({path: 'home'})
//命名的路由
this.$router.push({name: 'user', params: {userId}: '123'})
//带查询参数,变成 /register?plan=private
this.$push({path: 'register', query:{plan: 'private'}})
前进后退
//在浏览器记录中前进一步,等同于history.forward()
router.go(1)
//后退一步记录,等同于history.back()
router.go(-1)
//前进3步记录
router.go(3)
//如果history记录不够用,那就默认失败
router.go(-100)
router.go(100)
嵌套路由
router.js
{
path: '/user/:id',
name: 'user',
component: User,
props: ({params,query})=>({
id: params.id,
title:query.title
}),
children:[
// 当 /user/:id/profile 匹配成功,
// Profile 会被渲染在 User 的 <router-view> 中
{
path:"profile",
component: Profile
},
// 当 /user/:id/posts 匹配成功,
// Posts 会被渲染在 User 的 <router-view> 中
{
path: "posts",
component: Posts
}
]
}
在User
组件的模板添加一个<router-view>
:
<template>
<div>
<h3>用户页面{{$route.params.id}}</h3>
<h3>用户页面{{id}}</h3>
<router-view></router-view>
</div>
</template>
App.vue
<template>
<div id='app'>
<!-- 嵌套路由 -->
<router-link to="/user/1/profile">User/profile</router-link> |
<router-link to="/user/1/posts">User/posts</router-link> |
</div>
</template>
命名视图
有时候想同时 (同级) 展示多个视图,⽽不是嵌套展示,例如创建⼀个布局,有 sidebar
(侧导航) 和 main
(主内容) 两个视图,这个时候命名视图就派上⽤场了。
{
path: '/home',
name: 'home',
//注意这个key是components
components: {
default: Home, //默认的名字
main: ()=>import('@/views/Main.vue'),
sidebar: ()=>import('@/views/Sidebar.vue')
}
}
App.vue
<router-view/>
<router-view name='main'/>
<router-view name='sidebar'/>
导航守卫
“导航”表示路由正在发生改变。
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的
beforeEach
守卫 - 在重用的组件里调用
beforeRouterUpdate
守卫(2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouterEnter
。 - 调用全局的
beforeResolve
守卫(2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发DOM更新。
- 用创建好的实例调用
beforeRouterEnter
守卫中传给next
的回调函数。
全局守卫
你可以使用router.beforeEach
注册一个全局前置守卫
const router = new VueRouter({...})
router.beforeEach((to, form, next) => {
//...
})
有个需求,⽤户访问在浏览⽹站时,会访问很多组件,当⽤户跳转到 /notes
,发现⽤户没有登录,此时应该让⽤户登录才能查看,应该让⽤户跳转到登录⻚⾯,登录完成之后才可以查看我的笔记的内容,这个时候全局守卫起到了关键的作⽤。
有两个路由 /notes
和 /login
。
router.vue
const router = new VueRouter({
routes: [
{
path: '/notes',
name: 'notes',
component: () => import('@/views/Notes')
},
{
path: '/login',
name: 'login',
component: () => import('@/views/Login')
},
]
})
//全局守卫
router.beforeEach((to, from, next) => {
//用户访问的是'/notes'
if(to.path === '/notes') {
//查看一下用户是否保存了登录状态信息
let user =
JSON.parse(localStorage.getItem('user'))
if(user) {
//如果有,直接放行
next();
}else {
//如果没有,用户跳转登录页面登录
next('/login')
}
}else {
next();
}
})
Login.vue
<template>
<div>
<input type="text" v-model="username">
<input type="password" v-model="pwd">
<button @click="handleLogin">提交</button>
</div>
</template>
<script>
export default {
data() {
return {
username: "",
pwd: ""
};
},
methods: {
handleLogin() {
//1.获取用户名和密码
//2.与后端发生交互
setTimeout(() => {
let data = {
username: this.username
};
//保存用户登录信息
localStorage.setItem("user", JSON.stringfy(data));
//跳转我的笔记页面
this.$router.push({name: "notes"});
}, 1000);
},
}
}
</script>
App.vue
<!--全局守卫演示-->
<router-link to="/notes">我的笔记</router-link> |
<router-link to="/login">登录</router-link> |
<button @click="handleLogout">退出</button>
<script>
export default {
methods: {
handleLogout() {
//删除登录状态信息
localStorage.removeItem("user");
//跳转到⾸⻚
this.$router.push('/')
}
},
}
</script>
组件内的守卫
你可以在路由组件内直接定义以下路由导航守卫:
beforeRouteEnter
beforeRouteUpdate
(2.2新增)beforeRouteLeave
<template>
<div>
<h3>用户编辑页面</h3>
<textarea name id cols="30" rows="10" v-model="content"</textarea>
<button @click="saveData">保存</button>
<div class="wrap" v-for="(item, index) in list" :key="index">
<p>{{item.title}}</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
content: "",
list: [],
}
},
methods: {
saveData() {
this.list.push({
title: this.content
});
this.content = "";
}
},
beforeRouteLeave(to, from, next) {
//导航离开该组件的对应路由时调用
//可以访问组件实例`this`
if(this.content) {
alert("请确保信息已保存再离开");
next(false);
}else {
next();
}
}
};
</script>
路由元信息实现权限控制
给需要添加权限的路由设置meta字段
{
path: '/blog',
name: 'blog',
component:() => import('@/views/Blog'),
meta: {
requiresAuth: true
},
}
{
//路由独享的守卫
path: 'notes',
name: 'notes',
component: () => import('@/views/Notes'),
meta: {
requiresAuth: true
}
},
//全局守卫
router.beforeEach((to, from, next) => {
if(to.matched.some(record => record.meta.requiresAuth)) {
//需要权限
if(!localStorage.getItem('user')) {
next({
path: '/login',
query: {
redirect: to.fullPath
}
})
}else {
next();
}
}else {
next();
}
})
Login.vue
//登录操作
handleLogin() {
// 1.获取⽤户名和密码
// 2.与后端发⽣交互
setTimeout(() => {
let data = {
username: this.username
};
localStorage.setItem("user", JSON.stringify(data));
// 跳转到之前的⻚⾯
this.$router.push({path:this.$route.query.redirect });
},1000};
}
数据获取
有时候,进⼊某个路由后,需要从服务器获取数据。例如,在渲染⽤户信息时,你需要从服务器获取⽤户的数据。我们可以通过两种⽅式来实现:
- 导航完成之后获取:先完成导航,然后在接下来的组件⽣命周期钩⼦中获取数据。在数据获取期间显示“加载中”之类的指示。
- 导航完成之前获取:导航完成前,在路由进⼊的守卫中获取数据,在数据获取成功后执⾏导航。
导航完成后获取数据
当你使⽤这种⽅式时,我们会⻢上导航和渲染组件,然后在组件的 created
钩⼦中获取数据。这让我们有机会在数据获取期间展示⼀个 loading 状态,还可以在不同视图间展示不同的 loading 状态。
<template>
<div class="post">
<div v-if="loading" class="loading">Loading...</div>
<div v-if="error" class="error">{{ error }}</div>
<div v-if="post" class="content">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>
</div>
</template>
<script>
export default {
name: "Post",
data() {
return {
loading: false,
post: null,
error: null
};
},
// 组件创建完后获取数据,
// 此时data已经被监视了
created() {
// 如果路由有变化,会再次执⾏该⽅法
this.fetchData();
},
watch: {
$route: "fetchData"
},
methods: {
fetchData() {
this.error = this.post = null;
this.loading = true;
this.$http.get('/api/post').then((result) => {
this.loading = false;
this.post = result.data;
}).catch((err) => {
this.error = err.toString();
});
}
}
</script>
路由在项目中的封装使用
Vue3+Vue-Router封装,具体文件如下:
router/
—-afterGuard.js #后置路由守卫
—-frontGuard.js #前置路由守卫
—-index.js #路由管理
—-routes.js #路由文件
afterGuard.js
export default function afterGuard(router) {
router.afterEach((to, from, failure) => {
// do something
return true
});
};
frontGuard.js
export default function frontGuard(router) {
router.beforeEach((to, from) => {
// ...
// 返回 false 以取消导航
return true;
});
};
routes.js
import Index from "../layout/index.vue"
const routes = [
{
path: "/",
component: Index,
children: []
},
];
export default routes;
index.js
import { createRouter, createWebHashHistory } from "vue-router";
import routes from "./routes";
import frontGuard from './frontGuard'
import afterGuard from './afterGuard'
const router = createRouter({
history: createWebHashHistory(),
routes,
});
// 应用前置路由守卫
frontGuard(router)
// 应用后置路由守卫
afterGuard(router)
const useRouter = function(app){
app.use(router)
}
export default useRouter;
注册路由
main.js
import { createApp } from "vue";
import App from "./App.vue";
import useRouter from './router/index'
const app = createApp(App);
// 注册路由
useRouter(app)
app.mount("#app");