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 = truethis.$refs.msgSuccess.toggle();} else {// 显示错误信息// this.showWarn = truethis.$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表示组件初始化时也执行handlerimmediate: 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=privaterouter.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 = truethis.$router.push(this.$route.query.redirect)},logout () {window.isLogin = falsethis.$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)}}},
组件内守卫
可以在路由组件内直接定义以下路由导航守卫:
beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave// About.vuebeforeRouteEnter(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() }) },
- 路由导航后```javascriptcreated() {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用户登录成功后动态添加/aboutlogin() {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")
