Vue预习课:路由
Vue Router
Vue Router 是 Vue.js 官方的路由管理器。(为了单页面应用程序在不同的视图之间切换,所以需要路由这个库 )
可以查看官方文档看这个库的介绍
安装
vue-cli的环境下,router的安装,其他环境的安装参考文档
vue add router
基础
起步
路由规划、配置,router/index.js
商品列表(home) - 商品管理(about)
路由出口、导航,App.vue
<nav>
<!-- 导航链接, to是链接到哪个地址 -->
<router-link to="/">首页</router-link>
<router-link to="/about">管理</router-link>
</nav>
<!-- 路由出口 -->
<router-view></router-view>
商品管理,About.vue
<template>
<div>
<message ref="msgSuccess" class="success">
<!-- 命名为title插槽内容 -->
<template v-slot:title="slotProps">
<strong>{{slotProps.title}}</strong>
</template>
<!-- 默认插槽内容 -->
<template v-slot:default>新增课程成功!</template>
</message>
<message ref="msgWarning" class="warning">
<!-- 命名为title插槽内容 -->
<template v-slot:title>
<strong>警告</strong>
</template>
<!-- 默认插槽内容 -->
<template v-slot:default>请输入课程名称!</template>
</message>
<cart-add v-model="course" @add-course="addCourse"></cart-add>
<course-list :courses="courses"></course-list>
</div>
</template>
<script>
import CartAdd from "@/components/CartAdd.vue";
import CourseList from "@/components/CourseList.vue";
import Message from "@/components/Message.vue";
import { getCourses } from "@/api/course";
export default {
name: "app",
data () {
return {
course: "",
courses: []
};
},
components: {
CartAdd,
CourseList,
Message
},
async created () {
// 组件实例已创建,由于未挂载,dom不存在
const courses = await getCourses();
this.courses = courses;
},
methods: {
addCourse () {
if (this.course) {
// 添加course到数组
this.courses.push({ name: this.course, price: 8999 });
this.course = "";
// 显示提示信息
// this.show = true
this.$refs.msgSuccess.toggle();
} else {
// 显示错误信息
// this.showWarn = true
this.$refs.msgWarning.toggle();
}
}
}
};
</script>
动态路由匹配
我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果:
{ path: '/user/:id', component: User }
// 也可以是多个不同参数/user/:id/:name
范例:查看课程详情,views/Detail.vue
<div>
<h2>detail page</h2>
<!-- 获取动态路由匹配中通过路由传递的参数 -->
<p>{{$route.params.name}} ...</p>
</div>
router/index.js
{
path: '/course/:name',
// 懒加载组件,增加初始化页面的速度,减少初始化页面大小的体积,增强用户的体验
// 异步加载组件
component: () => import('../views/Detail.vue')
}
列表中的导航,About.vue
// 使用了反单引号
<router-link :to="`/course/${c.name}`">
{{ c.name }} - {{ c.price | currency('¥') }}
</router-link>
通配符
适合做 404 页面路由
路由的匹配是从上往下找的,如果路由中写了两个相同路径的路由配置,则以最上面的为准.当路由配置中都匹配结束之后都没有找到合适的匹配项,则使用通配符匹配的路径
{
// 会匹配所有路径
path: '*',
component: () => import('../views/404.vue')
}
嵌套路由
实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件,例如:
范例:嵌套方式显示课程详情
<router-link :to="`/about/${c.name}`">
{{ c.name }} - {{ c.price | currency('¥') }}
</router-link>
<!-- 如果路由配置中配置了组件,则在下面router-view的区域展示,否则不展示 -->
<!-- 嵌套路由出口 -->
<router-view></router-view>
路由配置
{
// path可以写相对路径,也可以写绝对路径,建议绝对路径
path: '/about',
name: 'about',
component: () => import(/* webpackChunkName: "about" */
'../views/About.vue'),
children: [
{
path: ':name',
component: () => import('../views/Detail.vue')
},
]
}
响应路由参数变化,Detail.vue
// 因为在组件创建的时候,需要获取相关信息。但是因为为了提升代码执行效率,在组件复用的情况下,该组件不会被销毁和重建,所以这里需要监听路由的变化。 immediate表示组件初始化时也执行handler
export default {
watch: {
$route: {
handler: () => {
console.log("$route change");
},
// immediate表示组件初始化时也执行handler
immediate: true
}
}
};
编程导航
借助 router 的实例方法,可编写代码来实现编程式导航
路由跳转location: 地址(想要访问的地址)
onComplete:路由完成之后回调函数
onAbort: 用户取消之后回调函数
router.push(location, onComplete?, onAbort?)
(底层使用了historyAPI的方法)
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
范例:修改为课程详情跳转为编程导航
<!-- @click中可以写多个语句,建议不要这么使用 -->
<div @click="selectedCourse = c;$router.push(`/about/${c.name}`)">
{{ c.name }} - {{ c.price | currency('¥') }}</div>
在实际工作中使用路由的名称的频率较高,因为路径太长
命名路由
建议使用下面的方式
通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。你可以在创建 Router 实例的时候,在 routes
配置中给某个路由设置名称。
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})
要链接到一个命名路由,可以给 router-link
的 to
属性传一个对象:
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
调用 router.push() 时:
router.push({ name: 'user', params: { userId: 123 }})
如果遇到切换路由组件不重新渲染的问题,解决方案如下:
// 因为在组件创建的时候,需要获取相关信息。但是因为为了提升代码执行效率,在组件复用的情况下,该组件不会被销毁和重建,所以这里需要监听路由的变化
watch: {
// 监听路由
$route: {
handler () {
console.log(`根据传输的${this.$route.params.id}获取值`)
},
immediately: true // 初始化的时候也执行
}
}
进阶
路由守卫
vue-router
提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。(为了让没有权限访问某个路径的用户,无法访问哪个路径,也就是路由的权限问题)
全局守卫
// (to(去哪里), from(来自哪里), next(是否放行))
router.beforeEach((to, from, next) => {
// ...
// to: Route: 即将要进入的目标 路由对象
// from: Route: 当前导航正要离开的路由
// next: Function: 一定要调用该方法来 resolve 这个钩子。
})
范例:守卫About.vue
router.beforeEach((to, from, next) => {
// 判断路由看是否需要守卫
// 是否登录
if (to.meta.auth) {
if (window.isLogin) {
// 放行
next()
} else {
// to.fullPath为了让用户登录之后可以跳转到原来它想去的页面
next('/login?redirect=' + to.fullPath)
}
} else {
// 放行
next()
}
})
{
path: '/about',
meta: {
auth: true
}
},
{
path: '/login',
// 异步
component: () => import('../views/Login.vue')
},
<template>
<div>
<button @click="login" v-if="!isLogin">登录</button>
<button @click="logout" v-else>登出</button>
</div>
</template>
<script>
export default {
methods: {
login () {
window.isLogin = true
this.$router.push(this.$route.query.redirect)
},
logout () {
window.isLogin = false
this.$router.push('/')
}
},
computed: {
isLogin () {
return window.isLogin
}
},
}
</script>
路由独享的守卫
可以路由配置上直接定义 beforeEnter
守卫:
{
path: '/about',
name: 'about',
// ...
beforeEnter (to, from, next) {
if (window.isLogin) {
next()
} else {
next('/login?redirect=' + to.fullPath)
}
}
},
组件内守卫
可以在路由组件内直接定义以下路由导航守卫:
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
// About.vue
beforeRouteEnter(to, from, next) {
if (window.isLogin) {
next();
} else {
next("/login?redirect=" + to.fullPath);
}
}
数据获取
每次切换路由,组件默认情况下总是会重新创建,数据总是会重新加载 1.每次切换路由获取数据是否必要 2.获取数据的时间点是否合适
路由激活时,获取数据的时机有两个:
- 路由导航前(组件没有渲染) ```javascript // 组件未渲染,通过给next传递回调访问组件实例 beforeRouteEnter(to, from, next) { getPost(to.params.id, post => { // 当前组件渲染完成之后,vm是当前组件实例 next(vm => vm.setData(post)) }) },
// 组件已渲染,可以访问this直接赋值 beforeRouteUpdate(to, from, next) { this.post = null getPost(to.params.id, post => { this.setData(post) next() }) },
- 路由导航后
```javascript
created() {
this.fetchData()
},
watch: {
// 监听路由变化
'$route': 'fetchData'
}
动态路由 —- 动态配置路由
动态添加路由。接收的参数是数组 动态配置路由
通过router.addRoutes(routes)方式动态添加路由
// 全局守卫修改为:要求用户必须登录,否则只能去登录页
router.beforeEach((to, from, next) => {
// 判断逻辑:
// 是否登录
if (window.isLogin) {
if (to.path === '/login') {
next('/')
} else {
next()
}
} else {
// 没有登录
if (to.path === '/login') {
next()
} else {
next('/login?redirect=' + to.fullPath)
}
}
})
在登录页面登陆成功之后,动态添加路由
// Login.vue用户登录成功后动态添加/about
login() {
window.isLogin = true;
// 动态添加路由
this.$router.addRoutes([
{
path: "/about", //...
}
]);
const redirect = this.$route.query.redirect || "/";
this.$router.push(redirect);
}
路由组件缓存
路由切换,组件频繁加载,数据频繁加载,浪费资源,可以使用组件缓存.让组件不重置状态,不销毁组件
利用keepalive
做组件缓存,保留组件状态,提高执行效率
可以使用include
来配置存活的组件,组件直接使用逗号隔开。exclude
来配置不需要存活的组件
缓存东西越多,占用资源越大。可以使用max
,max传入的是缓存组件的个数,如果配置的是10个,则超出10个的就进入一个新的缓存组件,出去一个最老的缓存组件,保证资源的合理利用
keep-alive 这个组件对组件中的name有依赖 include:配置能存活的组件(值是:用逗号隔开的字符串,每个字符串表示在创建组件时,在组件内部写的name,不是在路由中配置的name。值如果是动态的,则可以是数组,如下:)
exclude:排除某几个 max:最大缓存的数量。max传入的是缓存组件的个数,如果配置的是10个,则超出10个的就进入一个新的缓存组件,出去一个最老的缓存组件,保证资源的合理利用
范例:缓存about组件
<keep-alive include="about">
<router-view></router-view>
</keep-alive>
也可以使用下面的写法
<keep-alive :include="['about']">
<router-view></router-view>
</keep-alive>
使用include或exclude时要给组件设置name,不是配置路由的时候的那个name
两个特别的钩子:activated、deactivated
在keep-alive组件中存在activated、deactivated这两个钩子
activated:组件激活状态
deactivated:组件失活状态
路由懒加载
路由组件的懒加载能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
() => import("../views/About.vue")
把组件按组分块
() => import(/* webpackChunkName: "group-about" */ "../views/About.vue")