切换底部导航栏
预期效果
项目需要实现 【home首页】页面(已经浏览了部分内容) —— >切换到【my我的】页面(查看设置信息) ———> 切换回【home首页】,实现【home首页】不刷新,并保留页面浏览滚动的位置
页面整体架构
src/router/index.js
const routes = [
{ path: '/login', name: 'login', component: () => import('@/views/login') },
{
path: '/',
// name: 'layout',如果有默认子路由,则此处的name没有意义
component: () => import('@/views/layout'),
children: [
{
path: '', // 默认子路由,只能有一个
name: 'home',
component: () => import('@/views/home')
},
...省略其他代码
{
path: 'my',
name: 'my',
component: () => import('@/views/my')
}
]
}
]
src/App.vue
<template>
<div id="app">
<router-view>
</router-view>
</div>
</template>
src/layout/index.vue
<template>
<div class="layout-container">
<!-- 子路由 -->
<router-view>
</router-view>
<!-- 标签导航栏 -->
<!--route: 开启路由模式-->
<van-tabbar class="layout-tabbar" route>
<van-tabbar-item to="/">
<i slot="icon" class="toutiao toutiao-shouye"></i>
<span class="text">首页</span>
</van-tabbar-item>
...省略其他代码
<van-tabbar-item to="/my">
<i slot="icon" class="toutiao toutiao-wode"></i>
<span class="text">我的</span>
</van-tabbar-item>
</van-tabbar>
</div>
</template>
src/home/index.vue---首页组件
<template>
<div class="home-container">
<!-- 导航栏 ---------------------------------------->
<van-nav-bar />
<!-- 频道列表 ---------------------------------------->
<!--
<van-tabs v-model="active" animated swipeable>
<van-tab :key="item.id" v-for="item in channels" :title="item.name">
<!-- 频道的文章列表-----article-list列表子组件-------------------------- -->
<article-list :channel="item"></article-list>
</van-tab>
<!-- 占位元素 -->
<div slot="nav-right" class="placeholder"></div>
<div slot="nav-right" class="hamburger-btn">
<i class="toutiao toutiao-gengduo"></i>
</div>
</van-tabs>
</div>
</template>
src/home/components/article-list.vue
<template>
<div class="article-list" ref="articleListRef">
...省略代码
</div>
</template>
keep-alive知识补充
什么是KeepAlive
首先,我们要明确我们谈的是TCP的 KeepAlive 还是HTTP的 Keep-Alive。TCP的KeepAlive和HTTP的Keep-Alive是完全不同的概念,不能混为一谈。实际上HTTP的KeepAlive写法是Keep-Alive,跟TCP的KeepAlive写法上也有不同。
-
TCP的keepalive是侧重在保持客户端和服务端的连接,一方会不定期发送心跳包给另一方,当一方端掉的时候,没有断掉的定时发送几次心跳包,如果间隔发送几次,对方都返回的是RST,而不是ACK,那么就释放当前链接。设想一下,如果tcp层没有keepalive的机制,一旦一方断开连接却没有发送FIN给另外一方的话,那么另外一方会一直以为这个连接还是存活的,几天,几月。那么这对服务器资源的影响是很大的。
-
HTTP的keep-alive一般我们都会带上中间的横杠,普通的http连接是客户端连接上服务端,然后结束请求后,由客户端或者服务端进行http连接的关闭。下次再发送请求的时候,客户端再发起一个连接,传送数据,关闭连接。这么个流程反复。但是一旦客户端发送connection:keep-alive头给服务端,且服务端也接受这个keep-alive的话,两边对上暗号,这个连接就可以复用了,一个http处理完之后,另外一个http数据直接从这个连接走了。减少新建和断开TCP连接的消耗。
二者的作用简单来说:
HTTP协议的Keep-Alive意图在于短时间内连接复用,希望可以短时间内在同一个连接上进行多次请求/响应。
TCP的KeepAlive机制意图在于保活、心跳,检测连接错误。当一个TCP连接两端长时间没有数据传输时(通常默认配置是2小时),发送keepalive探针,探测链接是否存活。
总之,记住HTTP的Keep-Alive和TCP的KeepAlive不是一回事。
tcp的keepalive是在ESTABLISH状态的时候,双方如何检测连接的可用行。而http的keep-alive说的是如何避免进行重复的TCP三次握手和四次挥手的环节。
Vue中的KeepAlive
vue可以通过
<keep-alive>元素包裹组件,实现缓存,下次使用时不需要重新创建该组件。但存在一个问题:keep-alive包裹的组件中有滚动元素时,
keep-alive不会储存滚动位置。实现切换(后退)不刷新主要依据keep-alive组件的activated和deactivated这两个生命周期钩子函数。
vue钩子函数的执行顺序:
-
不使用keep-alive
- beforeRouteEnter --> created --> mounted --> destroyed
-
使用keep-alive
-
初次进入页面,beforeRouteEnter --> created --> mounted --> activated --> deactivated
-
再次进入缓存的页面,只会触发beforeRouteEnter -->activated --> deactivated。created和mounted不会再执行。
activated在keep-alive组件激活时调用deactivated在keep-alive组件被停用时调用.
-
项目实际应用
在该项目中,首先需要
缓存layout路由的数据,不需要缓存login路由的数据其次,进入layout路由后,只需要
记录首页my路由下缓存的数据,其他页面(如my)的数据不需要缓存
- 为需要缓存的页面,添加
meta: { keepAlive: true }属性
src/router/index.js
const routes = [
{ path: '/login', name: 'login', component: () => import('@/views/login') },
{
path: '/',
component: () => import('@/views/layout'),
* meta: { keepAlive: true },// 需要被缓存
children: [
{
path: '',
name: 'home',
* meta: { keepAlive: true },// 需要被缓存
component: () => import('@/views/home')
},
...省略其他代码
- 使用keep-alive缓存对应的组件
App.vue
<template>
<div id="app">
<keep-alive>
<router-view v-if="$route.meta.keepAlive">
<!-- 这里是会被缓存的视图组件,比如 Layout! -->
</router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive">
<!-- 这里是不被缓存的视图组件,比如 Login! -->
</router-view>
</div>
</template>
layout.vue
<template>
<div class="layout-container">
<keep-alive>
<!-- 子路由 -->
<router-view v-if="$route.meta.keepAlive">
<!-- 这里是会被缓存的视图组件,比如 Home! -->
</router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive">
<!-- 这里是不被缓存的视图组件,比如 qa! -->
</router-view>
<!-- 标签导航栏 -->
...省略其他代码
或者通过name来找到对应的组件
# 缺点:需要知道组件的 name,项目复杂的时候不是很好的选择
<keep-alive include="a">
<component>
<!-- name 为 a 的组件将被缓存! -->
</component>
</keep-alive>可以保留它的状态或避免重新渲染
<keep-alive exclude="a">
<component>
<!-- 除了 name 为 a 的组件都将被缓存! -->
</component>
</keep-alive>可以保留它的状态或避免重新渲染
- 通过记录
scollTop,来保持滚动位置(滚动的是home组件里article-list组件的区域) src/home/components/article-list.vue
<template>
<div class="article-list" ref="articleListRef">
...省略代码
</div>
</template>
<script>
import { getArticles } from '@/api/article.js'
// 导入 文章列表项组件
import ArcticleItem from '@/components/article-item'
export default {
data() {
return {
....
scrollTop: 0 // 记录页面滑动的位置
....
}
},
activated() {
// 该路由组件激活时,记录滚动位置
this.$refs.articleListRef.scrollTop = this.scrollTop
},
deactivated() {},
mounted() {
// this指向组件的实例,$el指向当前组件的DOM元素
// this.$el 等价于 this.$refs.articleListRef
// 拿到 当前滑动list的dom元素
const artListDom = this.$refs.articleListRef
// 当其滚动时,记录滚动后的top位置
artListDom.addEventListener('scroll', () => {
this.scrollTop = artListDom.scrollTop
})
},
参考文章