vue实现Tab切换功能
在项目开发中,我们经常会碰到Tab切换的功能,而在Vue中想实现这样的功能也应该有很多种,常用的三种应该是 Tab路由切换、Tab动态组件切换、通过v-show设置Tab显示隐藏。每种方法实现起来其实都不难,看看官网介绍或看几篇博客应该就能实现。
但这里面其实还有很多细节需要我们去做,如
- Tab切换时,切换过的Tab组件状态怎样缓存
- 在项目中经常会有 页面A -> 页面B -> 页面C 则从 页面C 返回 页面B 时 页面B 使用缓存数据,而页面A 跳到 页面B 时,则页面B每次都请求最新数据。比如我们在某APP内点击 最新新闻(页面A) 选项 跳转到 新闻列表(页面B) 选择某一条新闻 跳转到 新闻详情(页面C) 页面,我们希望,从新闻详情返回到新闻列表时,直接用刚才请求的数据,而不每次都重新发送请求,而从 最新新闻 跳转到 新闻列表时,则都请求最新的数据
- 父组件如何给子组件传递参数
- 页面内Tab来回切换后,如何直接返回到上一级页面
- 页面循环切换时,前进或后退如何保证页面结构正确(具体下面会讲到)
Tab路由切换带缓存
想要通过路由进行切换,就需要使用嵌套路由,即整个大页面是一个路由,点击不同Tab时,再通过嵌套路由来切换不同的路由。
想要Tab切换时保存当前状态可以使用keep-alive包裹,keep-alive具体使用参考这篇文章-vue中动态添加和删除组件缓存 keep-alive
包裹Tab的组件页面我们也要动态的缓存,这里也需要用到keep-alive,只是这个keep-alive需要添加到App.vue内,各个组件的动态缓存我们使用的是keep-alive的include属性。缓存最大数使用max属性
router-link介绍
- 通过to属性链接目标路由,当被点击时,内部会立刻把 to 的值传到 router.push(),既然是通过router.push()的方式跳转,那么就会往history记录中添加,这样当返回时,可能就会先从Tab3返回到Tab2再返回到Tab1再返回,这种体验很不好,怎样一步返回呢,就是在router-link中添加replace属性,这样当点击时,会调用 router.replace() 而不是 router.push(),于是导航后不会留下 history 记录,这样就可一步返回了,如:
<router-link :to="{ path: '/abc'}" replace></router-link>
- 通过 命名的路由 传递参数,如:
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
- 通过 带查询参数 传递参数,如:
<router-link :to="{ path: 'register', query: { plan: 'private' }}">Register</router-link>
,结果路由为:/register?plan=private
- router-link设置点击事件时需要添加 natvie, 如
@click.native="TabClick()"
思路
- 通过router配置嵌套路由
- 通过使用keep-alive的include属性有条件的缓存组件
- 通过store响应式的修改include属性对应的值
- 通过组件内导航钩子beforeRouteEnter、beforeRouteLeave给store提交mutations修改
实例演示
1:page1->news->page2 然后再依次返回
通过演示我们发现
- 从page2返回到news时,总是能返回到我们之前保存的状态
- 从news返回到page1后,再从page1跳转news,不管news之前是什么状态,都会初始化显示购物的页面
2:page1(1)->news(2)->page2(3)->page1(4)->news(5)->page2(6) 然后再依次返回
这个视频里有几个问题需要我们去思考
- 第四步跳转到第五步,为什么Tab选中为购物、内容选中为鞋包,为什么news组件及内部路由组件都缓存着
- 第三步返回到第二步,为什么Tab选中为购物、内容选中为母婴,但从右边缓存的组件看,为什么shopping组件也被缓存了
这两个问题我们后边会具体介绍
部分代码示例
1:在router中配置各个路由
这里需要注意,配置children子路由时path不能加 / ,在router-link的to后面写的路由需要以 / 开头,以 / 开头的嵌套路径会被当作根路径
export default new Router({routes: [{path: '/page1',name: 'page1',component: () => import(/* webpackChunkName: "test" */ './views/news/page1.vue')}, {path: '/page2',name: 'page2',component: () => import(/* webpackChunkName: "test" */ './views/news/page2.vue')}, {path: '/news',name: 'newsIndex',component: () => import(/* webpackChunkName: "test" */ './views/news/news.vue'),children: [{path: 'sports',name: 'sports',component: () => import(/* webpackChunkName: "test" */ './views/news/sports.vue'),}, {path: 'shopping',name: 'shopping',component: () => import(/* webpackChunkName: "test" */ './views/news/shopping.vue'),}, {path: 'learn',name: 'learn',component: () => import(/* webpackChunkName: "test" */ './views/news/learn.vue'),}]}]
})
2:在App.vue组件通过computed计算属性响应式的获取store里的keepAliveArr计算属性,并赋值给keep-alive的include属性,并设置最多可缓存5个组件
<template><div id="app"><keep-alive :include="keepAliveArr" :max="5"><router-view></router-view></keep-alive></div></template><script>export default {computed: {keepAliveArr() {return this.$store.getters.keepAliveArr}}}
3:在store的mutations中提供状态更改的方法,并通过store的计算属性供外部访问
import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex);export default new Vuex.Store({state: {//缓存组件数组keepAliveArr: []},mutations: {UPDATE_KEEP_ALIVE(state, payload) {//当payload.type不为空则代表清除指定缓存组件,否则添加指定组件if (payload.type) {let index = state.keepAliveArr.indexOf(payload.keepAlive);if (index !== -1) {state.keepAliveArr.splice(index, 1); //删除数组的缓存的组件}} else {let index = state.keepAliveArr.indexOf(payload.keepAlive);if (index === -1) {state.keepAliveArr.push(payload.keepAlive); //添加需要缓存的组件}}}},getters: {keepAliveArr: state => state.keepAliveArr}})
4:在组件内通过导航钩子beforeRouteEnter、beforeRouteLeave给store提交mutations修改缓存组件keepAliveArr的值
这里 page1为news的上一个页面,page2为下一个页面,通过beforeRouteEnter钩子,不管从哪个页面进入都提交mutations,缓存当前news页面,当离开时判断,如果是返回上一个页面则删除当前news页面缓存,当删除news页缓存时,内部通过keep-alive保存的 购物、体育、学习三个组件缓存的状态也会一并删除,即内部的在激活的和被停用的组件都会执行销毁的生命周期。
注意:如果我们的页面比较简单,最深跳转到page2,即: page1->news->page2,然后再一级一级返回的话,那么beforeRouteEnter这两个if判断可以不写
4.1:beforeRouteEnter中两个if判断解释
4.1.1:第一个if判断
当循环跳转时,即
page1(1)->news(2)->page2(3)->page1(4)->news(5)
因为page1跳转的路径 永远是 /news/shopping,news组件又通过keep-alive保存当前状态,所以在第二步news内如果 点击了 Tab体育 或者 Tab学习时,此时currentTab不为0,但当通过 第三步->第四步->第五步 再次跳转到news 时,由于page1路径 永远是 /news/shopping,而news状态还保存在内存里不会重新 创建,此时Tab指示器显示的和下面具体内容就会不一致,所以这里判断如果是这种情况就 强制切换 到 /news/shopping 页
4.1.2: 第二个if判断
当循环跳转时又依次返回时。即
page1(1)->news(2 Tab选择 学习)->page2(3)->page1(4)->news(5 Tab选择 体育)->page2(6)
现在开始返回, 返回到第五步Tab体育时,是没有问题的,因为 news状态是缓存 的, 而第五步Tab体育页返回第四步page1时,这里 beforeRouteLeave中我们已经把news页设置不缓存 了,再继续返回到第三步page2,再返到第二步Tab学习页时,因为 最初 我们是从第二步Tab学习的路由往 下一页page2页跳的,所以这里返回也是返回到Tab学习的路由页即 /news/learn ,但因为整个news已经不缓存了,所以这里返回从第三步返回到第二步时,其实 news所有的生命周期都会执行 ,此时 currentTab的值为0,如果不通过这个判断,那么Tab指示器显示的和下面具体内容也会不一致,所以这种情况我也 强制让切换 到 /news/shopping 页
4.2:组件内导航守卫的to.path 和 to.fullPath 区别?
- to.path: 是我们在router路由里定义的路由,如/news/shopping
- to.fullPath: 是包括我们跳转路由时传递的参数,如/news/shopping?content=购物
beforeRouteEnter(to, from, next) {next(vm => { //添加组件缓存vm.$store.commit("UPDATE_KEEP_ALIVE", {keepAlive: 'news'});let path = '';//当循环跳转时,替换路由为shopping页if (vm.currentTab !== 0 && from.path === '/page1') {vm.currentTab = 0;path = '/news/shopping';vm.$router.replace({path,query: {content: '购物'}});}//当循环跳转后,循环返回时,替换路由为shopping页if (vm.currentTab === 0 && to.path !== '/news/shopping') {vm.currentTab = 0;path = '/news/shopping';vm.$router.replace({path,query: {content: '购物'}});}})},beforeRouteLeave(to, from, next) {if (to.path === '/page1') {//删除缓存this.$store.commit("UPDATE_KEEP_ALIVE", {type: 1,keepAlive: 'goods'})}next()},
总结
通过上面四步就可以实现Tab路由切换并带组件状态缓存,这个keep-alive嵌套keep-alive需要注意的事项,大家可以参考这篇文章-vue中动态添加和删除组件缓存 keep-alive
Tab动态组件切换
大家可以参考这篇文章-vue中动态添加和删除组件缓存 keep-alive
通过v-show设置Tab显示隐藏
这个就不写了,大家只要慢慢写应该都能实现,只是用这种方式实现不太优雅。
Tab路由切换的完成代码
news代码
<template><div class="list-container"><div class="btn" @click="btnJumpClick">跳转到page2详情页</div><nav class="tab-root"><!--通过query向子路由传递参数--><router-link v-for="(item,index) in routerList":key="index"class="tab-button":to="{path:item.url,query:{content:item.content}}"replace:class="{ active: currentTab === index }"@click.native="currentTab = index">{{item.tab}}</router-link></nav><keep-alive :include="cached" :max="3"><router-view class="view"></router-view></keep-alive></div></template><script>export default {name: "news",data() {return {currentTab: 0,cached: 'shopping,sports,learn',about: '/news/shopping',routerList: [{tab: '购物',url: '/news/shopping',content: '购物'}, {tab: '运动',url: '/news/sports',content: '运动'}, {tab: '学习',url: '/news/learn',content: '学习'}]}},activated() {console.log("--news--activated--");},deactivated() {console.log("--news--deactivated--");},beforeRouteEnter(to, from, next) {next(vm => { //添加组件缓存vm.$store.commit("UPDATE_KEEP_ALIVE", {keepAlive: 'news'});let path = '';//当循环跳转时,替换路由为shopping页if (vm.currentTab !== 0 && from.path === '/page1') {vm.currentTab = 0;path = '/news/shopping';vm.$router.replace({path,query: {content: '购物'}});}//当循环跳转后,循环返回时,替换路由为shopping页if (vm.currentTab === 0 && to.path !== '/news/shopping') {vm.currentTab = 0;path = '/news/shopping';vm.$router.replace({path,query: {content: '购物'}});}})},beforeRouteLeave(to, from, next) {if (to.path === '/page1') {//删除缓存this.$store.commit("UPDATE_KEEP_ALIVE", {type: 1,keepAlive: 'news'})}next()},methods: {btnJumpClick() {this.$router.push({path: '/page2'})},}}</script><style scoped lang="scss">.list-container {.btn {width: 100%;height: 40px;background: #f00;font-size: 20px;color: white;}.tab-root {display: flex;border-bottom: 1px solid #eee;}.tab-button {background: #fff;line-height: 40px;height: 40px;text-align: center;flex: 1;font-size: 15px;font-weight: normal;}.tab-button.active {font-size: 17px;font-weight: 500;border-bottom: 2px solid #f00;}}
shopping代码
<template><div class="recommends-tab"><ul class="recommends-sidebar"><li v-for="recommend in recommends":key="recommend.id":class="{ selected: recommend === selectedRecommend }"@click="selectedRecommend = recommend">{{ recommend.title }}</li></ul><div class="selected-recommend-container"><div class="selected-recommend"><div v-html="selectedRecommend.content"></div></div></div></div></template><script>export default {name: "shopping",props:{componentTabName:String},data() {return {recommends: [{id: 1,title: '母婴',content: '<p>儿童玩具、尿裤湿巾、奶粉辅食</p>'},{id: 2,title: '鞋包',content: '<p>功能箱包、人气热卖、服饰配件</p>'},{id: 3,title: '水果',content: '<p>瓜果桃李、海鲜水产、熟食凉菜</p>'}],selectedRecommend: {}}},beforeMount() {//获取通过路由传递过来的参数console.log(this.$route.query.content);},}</script><style scoped lang="scss">.recommends-tab {display: flex;}.recommends-sidebar {width: 20%;text-align: center;background: #eee;height: 100vh;}.recommends-sidebar li {height: 30px;line-height: 30px;}.recommends-sidebar li.selected {background: #fff;color: red;}.selected-recommend-container {padding-left: 10px;}</style>
page1跳转代码
btnLuYouClick() {this.$router.push({path: '/news/shopping',query: {content: '购物'}});}
page2跳转代码
btnJumpClick() {this.$router.push({path: '/page1'})},
sports和learn代码比较简单就不粘贴了
上面的代码应该已经够用,如果需要全部详细代码的就留言吧,我再单独发你。
vue实现Tab切换功能相关推荐
- vue 实现tab切换动态加载不同的组件
vue 实现tab切换动态加载不同的组件 使用vue中的is特性来加载不同的组件.具体看如下代码:这个功能对于vue比较复杂的页面可以使用上,可以把一个页面的功能拆分出来,使代码更简单.使用方式具体看 ...
- javascript回车完美实现tab切换功能
javascript回车完美实现tab切换功能 javascript通过回车实现tab切换功能,最经有一个项目是给化工厂做的在使用的过程中需要输入大量的数据,使用的都是小键盘区,在以前都是通过exce ...
- vue中tab切换前端实现_vue实现Tab切换功能
在项目开发中,我们经常会碰到Tab切换的功能,而在Vue中想实现这样的功能也应该有很多种,常用的三种应该是 Tab路由切换.Tab动态组件切换.通过v-show设置Tab显示隐藏.每种方法实现起来其实 ...
- vue中tab切换前端实现_使用vue实现tab操作
tab功能在网页中是比较常见的,那么用vue怎么实现tab操作呢,与jQuery实现tab的思路有什么区别呢? 在使用jQuery类库实现tab功能时,是获取鼠标在mousenter或click时的i ...
- VUE实现Tab切换
VUE中实现Tab切换方式,需要用到以下知识点: 1.动态绑定class,用于导航高亮显示 :class="{active:cur==0}"复制代码 2.click点击事件,用于改 ...
- vue制作tab切换(vuex + 动画)
先来看一下效果 这里使用了vuex 来处理数据. (1)在首页选择的时候,将选择的结果存入state里面. (2)进入到内部页面,再将state的值 赋值给当前要切换tab. 这里只写一下内部tab的 ...
- javascript 回车实现 tab 切换功能完美解决
最经有一个项目是给化工厂做的在使用的过程中需要输入大量的数据,使用的都是小键盘区,在以前都是通过excel录入数据的现在, 在网页上需要实现excel 那样的回车换行的功能在网上找了有关这方面的问题但 ...
- vue + element-ui tab切换
1.安装element-ui npm install element-ui --save 2.在main.js中引入element 和 css文件 // The Vue build version t ...
- vue实现中英文切换功能
1.首先在百度翻译的开放平台中,获取属于自己的APPid,没有注册先注册 2.得到了这些基本信息后,需引入md5插件npm install --save js-md5 3.请求接口及对应参数 通用翻译 ...
最新文章
- redis源码分析(beta版本)-redis实现的概述逻辑
- linux postgresql .run包卸载,linux下删除自带的postgresql 及全新安装
- 调用摄像头_摄像头 | 浏览器调用摄像头并实现截图保存的效果
- 电脑安全注意事项_松下洗衣机维修方法及注意事项
- msyql开启慢查询以及分析慢查询
- myeclipse下Tomcat java.lang.OutOfMemory Error: Java heap space
- NYNU_省赛选拔题(10)
- php 进程管理及操作
- 项目讨论:本地手机经销商怎样用移动站点开展本地营销?
- ISO/IEC 20000 信息技术(IT)服务管理体系及全套最新标准资料
- IK(反向动力学)简单原理与实现
- android webview 下载文件
- 突然发现一款优化神器
- opensparc中的crossbar
- python导库快捷指令、快速导库、dlib库
- kibana远端访问配置
- 如何验证Java 布尔类型的true = 1 ,false = 0
- 方舟搭建服务器显示mod出错,为什么我方舟进不去mod服务器 | 手游网游页游攻略大全...
- android 各别控件缩放,[翻译]Android单手指缩放-第二部分(Android one finger zoom tutorial – Part 2)...
- 强化学习泛化性 综述论文阅读 A SURVEY OF GENERALISATION IN DEEP REINFORCEMENT LEARNING