二. vue-router的嵌套
嵌套路由是一个很常见的功能, 比如主业引入了组件Home, 我们在Home里面引入了banner图组件. 这样就是组件的嵌套.
我们的路由映射规则是: 一个路径映射一个组件. 访问两个路径, 会分别渲染两个组件.
下面来实现嵌套路由,
第一步: 创建组件,创建两个组件
HomeBanner.vue组件
<template> <div> <h2>首页banner图</h2> <ul> <li>banner图1</li> <li>banner图2</li> <li>banner图3</li> <li>banner图4</li> </ul> </div> </template> <script> export default { name: "HomeBanner" } </script> <style scoped> </style>
HomeNews.vue组件
<template> <div> <h2>首页新闻</h2> <ul> <li>第一条新闻</li> <li>第二条新闻</li> <li>第三条新闻</li> <li>第四条新闻</li> </ul> </div> </template> <script> export default { name: "HomeNews" } </script> <style scoped> </style>
第二步: 创建组件路由
我们要在Home也添加子路由, 需要在路由里面增加一个children属性配置.
//引入子路由-使用懒加载的方式进行加载 const HomeBanner = ()=>import('../components/HomeBanner'); const HomeNews = () => import('../components/HomeNews'); { path: "/home", component: Home, children: [{ path:'', redirect: 'HomeNew' },{ path: 'HomeNew', //注意: 这里面没有/ component: HomeNews },{ path: 'HomeBanner', component: HomeBanner }] }
里面的路径依然是有两个部分:
- 一个是path路由路径: 这里需要注意的是,不需要在路径名的前面加/
- 另一个是component: 指定组件名称
第三步: 增加<router-link> 和 <router-view/>
我们要在Home页面展示子组件, 因此需要将子组件的展示放在页面上
<template> <div> <h2>这是Home主页</h2> <div>欢迎来到home主页</div> <router-link to="/home/homeNew">新闻</router-link> <router-link to="/home/homeBanner">Banner图</router-link> <router-view></router-view> </div> </template>
完成以后,效果如下:
三. vue-router参数传递
vue-router参数传递有两种方式: 第一种是: param的方式. 第二种是: query的方式
1. param方式
这种方式在动态路由的时候有提到过. 下面来回顾一下:
第一步: 创建一个User.vue组件
<template> <div> <h2>用户界面</h2> </div> </template> <script> export default { name: "User", } </script> <style scoped> </style>
第二步:创建动态路由的时候, 定义变量
const routes = [ { path: "/user/:userId", component: User } ]
我们定义了一个user/:userId, 这样的动态路由. 路由可以是/user/zhangsan, 或者/user/lisi
第三步: 在App.vue中定义动态路由跳转的组件
<template> <div id="app"> <!-- 定义两个路由链接 --> <router-link to="/home" tag="button" replace active-class="active">首页</router-link> <router-link to="/about" tag="button" replace active-class="active">关于</router-link> <!-- 定义动态路由 --> <router-link v-bind:to="'/user/' + userId" tag="button" replace active-class="active">用户</router-link> <!-- 展示组件内容 --> <router-view></router-view> </div> </template> <script> export default { name: 'App', data(){ return { userId:"zhangsan" } } } </script> <style> .active { color:red; } </style>
这里面userId是变化的部分. 通过bind事件动态将userId绑定到path路径中
<router-link v-bind:to="'/user/' + userId" tag="button" replace active-class="active">用户</router-link>
第四步: 修改User.vue组件
首先, 在User.vue脚本中获取传递过来的路由参数. 我们使用[this.$route.params.变量名]的方式获取路径参数
<script> export default { name: "User", data() { return { userId: this.$route.params.userId } } } </script>
然后使用语法糖, 将变量显示出来
<template> <div> <h2>用户界面</h2> <div>欢迎{{userId}},来到用户界面</div> <div>{{$route.params.userId}}</div> </div> </template>
也可以直接使用使用{{$route.params.userId}}的写法
以上是使用参数的方式传递变量.
2. query方式
第一步: 配置路由的时候是普通配置: /user
第二步:传递的方式使用query的方式, query是一个对象,
第三步:传递后形成的路径是:/user?useId=2, /user?userId=9
下面来举个案例:
第一步: 创建一个组件Profile.vue
<template> <div> <h2>这是一个Profile组件</h2> </div> </template> <script> export default { name: "Profile" } </script> <style scoped> </style>
第二步: 配置路由
// 配置路由相关的信息 // 导入vue和vue-router import Vue from 'vue' import Router from "vue-router"; // 这里会引入你要导入的组件, 然后通过路由配置组件内容 // 懒加载 const Profile = () => import('../components/Profile') /* * 第一步: 安装插件 * vue-router是一个插件, 所以, 我们需要使用vue.use(插件名) 来安装插件 */ Vue.use(Router) /* * 第二步: 构建VueRouter对象 * 在VueRouter中主要是配置路由和组件之间的映射关系的. */ const routes = [ { path: "/profile", component: Profile } ] const router = new Router({ // 这里配置的是路由和组件的映射关系, 是一个数组. routes, mode: "history", linkActiveClass: "active" }) /* * 第三步: 将VueRouter对象导出 * */ export default router
第三步: 渲染组件
<!-- 配置动态路由 --> <router-link v-bind:to="{path:'/profile', query:{name:'lily',sex:'男',age:12}}" tag="button" replace active-class="active">档案</router-link>
通常我们定义路由是这样定义的
<!-- 配置动态路由 --> <router-link to="/profile" tag="button" replace active-class="active">档案</router-link>
但是, 这样路由就固定写死了, 所以肯定不行, 我们需要给他一个变量, 使用v-bind:to
然后就有了下面这个写法
v-bind:to="{path:'/profile', query:{name:'lily',sex:'男',age:12}}"
第四步: 来看一下效果
我们看到路径上带了?参数.
http://localhost:8080/profile?name=lily&sex=%E7%94%B7&age=12
第五步: 在Profile组件中获取参数
获取参数有两种方式
- 直接语法糖方式获取{{$route.query.***}}
- 在脚本中获取:this.$route.query.***
如下采用方式一获取参数:
<template> <div> <h2>这是一个Profile组件</h2> <h4>{{$route.query}}</h4> <h4>{{$route.query.name}}</h4> <h4>{{$route.query.age}}</h4> <h4>{{$route.query.sex}}</h4> </div> </template>
第六步: 查看效果
四. vue-router导航守卫
导航守卫, 听者特别的高大上. 到底是什么意思呢?
在之前的html页面, 如果我们想要实现title跟着页面变, 那么只需要设置html页面的title属性就可以了.
在vue中, 只有一个index.html页面,如何实现title的改变呢?
有两种方法:
- 第一种, 使用生命周期函数created().
- 第二种: 使用全局导航守卫
1. 使用生命周期实现页面更新title属性
组件的生命周期, 这在之前有提到过. 子组件的生命周期中有很多函数, 比如created(), mouted(), updated(), 我们可以在他的生命周期增加处理逻辑.
,我们可以在created()方法中增加处理逻辑.
<template> <div> <h2>这是Home主页</h2> <div>欢迎来到home主页</div> <router-link to="/home/homeNew">新闻</router-link> <router-link to="/home/homeBanner">Banner图</router-link> <router-view></router-view> </div> </template> <script> export default { name: "Home", created() { document.title="首页" } } </script> <style scoped> </style>
我们看到在script中增加一个created方法. 并指定了title名称.
<script> export default { name: "Home", created() { document.title="首页" } } </script>
来看一下效果
这样有什么问题? 假如现在有100个页面, 我们需要在100个页面中都增加created()函数. 虽然可以实现功能,但似乎有些麻烦, 有没有可以统一修改的办法呢?我们可以使用全局导航守卫实现
3.2. 使用全局导航守卫的方式更新title属性
什么是导航守卫?
来看看官网的解释: https://router.vuejs.org/zh/guide/advanced/navigation-guards.html
正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
- 这里的导航,指的就是路由的跳转或者取消跳转
- 守卫指的是: 有很多钩子方法, 允许我们在某一个过程中植入代码逻辑.
- 常见的导航守卫有: 全局导航守卫(包括三个: 全局前置守卫, 全局解析守卫, 全局后置守卫), 路由独享导航守卫, 组件内的守卫.
下面老看看[全局前置守卫]
1. 使用 router.beforeEach 注册一个全局前置守卫
const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { // ... })
当一个导航触发时,全局前置守卫按照创建顺序调用。
守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
我们来看看router.beforeEach的实现.
点击beforeEach进入到router.ts文件, 可以看到里面定义了router.beforeEach()方法的定义
beforeEach(guard: NavigationGuard): Function
这是一个函数, 方法有一个入参, 参数是一个方法, 参数名是guard. 方法体是NavigationGuard. 我们来看看NavigationGuard的实现逻辑
export type NavigationGuard<V extends Vue = Vue> = ( to: Route, from: Route, next: NavigationGuardNext<V> ) => any
NavigationGuard也是一个函数, 并且这个函数继承自Vue. 函数有三个入参: to, from, next. 方法实现是any.
所以,我们要想重写beforeEach()方法. 那么参数就要定一个方法, 并且方法里有三个入参. 方法如下:
router.beforeEach(function(to, from, next) { })
除了这种方法, 我们还可以使用箭头函数
router.beforeEach((to, from, next) =>{ })
两种都是可以的.
接下来, 看看函数中三个参数都是什么含义呢?
- to: 是一个router对象, 含义是导航到的目标路径.
- from: 是一个router对象, 含义是当前导航正要离开的路由.
- next: 是一个函数, 这是一个钩子函数. 一定要调用该方法来 resolve 这个钩子
函数实现的部分, 一定要调用next()方法. 表示导航继续向下执行. 如果不调用next(), 那么后面的函数将不会被解析或者执行.
也就是说, 代码这至少是这样的
router.beforeEach((to, from, next) =>{ next(); })
确保 next 函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。
2. 在路由index.js文件中增加元数据属性, 并设置title属性值
const routes = [ { path:"/", redirect: "/home", }, { path: "/home", component: Home, children: [{ path:'', redirect: 'HomeNew' },{ path: 'homeNew', //注意: 这里面没有/ component: HomeNews },{ path: 'homeBanner', component: HomeBanner }], meta: { title: "首页" } },{ path: "/about", component: About, meta: { title: "关于" } },{ path: "/user/:userId", component: User, meta: { title: "用户" } },{ path: "/profile", component: Profile, meta: { title: "档案" } } ]
增加的元数据内容如下
meta: { title: "档案" }
3. 后面在全局导航路由中设置title属性
router.beforeEach((to, from, next) => { console.log(to) console.log(from) document.title = to.matched[0].meta.title next() })
我们通过打印to和from对象, 发现他们确实都是路由对象, 我们可以通过matched中的第0个元素,获取到meta中的内容.
我们看到其实to下面和matched平级的也有一个meta属性, 但是这个属性在某些情况下值是空的. 所以, 我们还通过matched的第一个元素来获取meta对象
以上就是全局前置导航守卫的用户, 后置导航守卫等其他守卫, 用法与其相似, 可以查看官方文档: https://router.vuejs.org/zh/guide/advanced/navigation-guards.html
前置守卫也好, 后置守卫也好, 都是路由组件的钩子函数, 通过钩子函数, 我们可以在对应的生命周期织入业务逻辑.
理解守卫的含义
全局前置守卫, 全局解析守卫, 全局后置守卫: 定义在路由文件中, 表示对所有路由都有效
路由独享守卫: 可以在路由配置上直接定义 beforeEnter 守卫:
组件内的守卫:你可以在路由组件内直接定义以下路由导航守卫, 有效范围是某个组件.
常见的组件内守卫: beforeRouteEnter(进入路由前),beforeRouteUpdate (更新路由前)),beforeRouteLeave(离开路有前)
五. keep-alive
我们有首页, 关于, 用户, 档案. 首页下面有两个按钮[新闻],[消息]
当点击首页的[消息], 然后切换到关于页面, 再回到首页的时候, 我们希望能够继续展示[消息]的内容
默认是不会保留操作的记忆的. 下次回来直接到[首页->新闻], 使用keep-alive就可以有保留记忆的效果了
为什么每次回到首页, 都会展示[首页->新闻]的内容呢?
原因是每次回到首页都会创建一个新的Home组件.
我们来验证每次回到首页都会重新创建一个新的组件. 来看看vue组件的生命周期. 其实Vue对象本身也是一个大组件
如上图所示:在vue整个生命周期, 他有很多挂载函数, 比如:beforeCreate(创建组件之前), created(创建组件后), beforeMount(挂载组件之前), mounted(挂载组件之后),
beforeUpdate(组件值更新之前),updatd(组件值更新之后),beforeDestroy(组件销毁之前),destroyed(组件销毁之后)
1. 验证, 是否销毁了原来的组件呢?
我们只需要验证created()和destroyed()两个函数是否每次跳走都会被执行一遍.
<script> export default { name: "Home", created() { document.title="首页" console.log("created home") }, destroyed() { console.log("destroyed home") } } </script>
如上所示:定义了创建函数和销毁函数, 下面来看看当跳走的时候, 是不是会执行这两个函数.
如上图所示: 当离开首页,就会执行destroyed函数, 当进入首页, 就会执行created函数. 说明每次确实都在创建新的组件
2. 如何才能让组件有记忆,而不是每次都重新创建呢?
如果想要实现路由跳转走以后, 返回来不需要重新创建组件, 我们可以使用keep-alive, 他的用法很简单
在组建展示的位置增加标签
<keep-alive> <router-view></router-view> </keep-alive>
这样调走再跳回来就不会重新创建组建了, 来看看效果
我们看到只有第一次创建了home组件, 后来路由调走, 组件并没有被销毁.
3.案例: 实现从[首页->banner图]跳走后, 在跳回来, 依然定位在[首页->banner图]的位置
跳走, 在跳回来, 这实际上是在控制路由.我们可以让路由调走之前记住当前组件的路由. 要想实现这个功能,需要了解一下几个钩子函数:
- activated: 路由激活时触发
- deactivated: 路由取消激活时触发
先来看这两个: 这两个函数生效的条件是 : 设置了<keep-alive>组件才有效. 也就是说, 组件离开时不销毁.
我们在home组件中增加两个方法
activated() { // 路由激活 console.log("activated home") }, deactivated() { // 路由取消激活 console.log("deactivated home") },
然后来看看效果: 我们发现跳到home路由, 触发activated方法, 离开home路由, 触发deactivated方法.
如何记住路由呢? 我们可以定义一个变量来记住调走前的路由.
this.$route.path : 他是获取当前激活的路由
要想实现这个功能, 还需要使用到路由守卫.
路由守卫有三种类型
const Foo = { template: `...`, beforeRouteEnter(to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 `this` // 因为当守卫执行前,组件实例还没被创建 }, beforeRouteUpdate(to, from, next) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 `this` }, beforeRouteLeave(to, from, next) { // 导航离开该组件的对应路由时调用 // 可以访问组件实例 `this` } }
我们选择使用最后一个beforeRouteLeave, 在离开当前路由的时候, 记录下离开前的路由.
代码实现如下:
1. 在home组件增加两个方法, 一个是activated组件激活的时候重定向路由, 另一个是beforeRouteLeave组件离开前记录离开前的路由
activated() { // 路由激活, 路由到path路径 this.$router.push(this.path) }, beforeRouteLeave(to, from, next) { console.log("离开home前的路径 "+this.path) this.path = this.$route.path; next() }
2. 看一下运行的效果:
以上就是keep-alive的内容, 这里重要要有一个概念, 组件调走以后是会销毁原来的组件的.如果我们不想它被销毁, 那么可以使用<keep-alive>组件实现.
4. keep-alive 的属性
keep-alive有两个属性: 一个是include, 另一个是exclude
- include: 字符串或者正则表达式, 只有匹配的组件才会被缓存
- exclude: 字符串或者正则表达式, 匹配到的组件不会被缓存.
比如,我们整个组件都希望每次加载会缓存, 但是档案组件特殊, 希望他每次都被销毁, 重新创建.
第一步: 在Profile组件中增加created和destroy事件
created() { console.log("Profile created") }, destroyed() { console.log("Profile destroyed") }
第二步: 在<keep-alive>中配置排除Profile组件
<keep-alive exclude="Profile"> <router-view></router-view> </keep-alive>
这样, Profile组件就不会被缓存了
第三步: 演示效果