Situation
首先该项目是一个h5项目,有一个滑动列表页,当我滑动点击列表项,会进入到详情页,当我退出的时候,我希望还能定位到我刚刚点进来的位置。
Task
我们的目的是定位滑动位置。在返回的时候应该回到刚刚的滑动位置。
所以有两个关键点,第一个是怎么定位滑动位置,第二是返回的时候怎么回到滑动位置。滑动位置的话我们可以获取到滑动容器的scrollTop值,然后返回的时候设置滑动容器的scrollTop就可以定位。
Action
1. 采用scrollBehavior 函数
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
}
})
scrollBehavior 方法接收 to 和 from 路由对象。第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。
所以在定义router的地方,挂载了scrollBehavior函数。
- 问题:savedPosition打印出来的x,y坐标,不是想要的,不准确。
- 分析:因为scrollBehavior是挂载在路由身上,它对应的应该是一个整个路由组件,而我的滑动区域是组件里面的一个固定区域,也就是说,我应该把scrollTop挂在滑动区域,但是整个页面结构不允许我这样做。所以尝试第二种方法,keep-alive
2. keep-alive
Keep-alive 是什么?这可不是http保持长链接的keep-alive哦
keepalive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染 。也就是所谓的组件缓存
但是有个问题是,它不会缓存我们的滑动位置,我们需要手动记录位置。
a. 在滑动的时候记录滑动组件的scrollTop (这里可以加一个节流函数)
onScroll(e: Event) {
const { scrollTop } = e.target as HTMLElement;
this.scrollTop = scrollTop; // 记录滑动位置,回来时恢复到此位置
}
keep-alive的声明周期执行
- 页面第一次进入,钩子的触发顺序:
created-> mounted-> activated,
退出时触发 deactivated 当再次进入(前进或者后退)时,只触发 activated- 事件挂载的方法等,只执行一次的放在 mounted 中;组件每次进去执行的方法放在 activated 中;
b. 所以在activated声明周期中定位位置
activated() {
// 恢复之前保存的位置
(this.$refs.scroller as HTMLElement).scrollTop = this.scrollTop;
}
这样,我们的问题是解决了,但是也带来了新的问题,当我滑动列表后,再退出,进入首页,再从首页进入列表页,此时会缓存刚刚的滑动位置,并且数据也是缓存的,不是最新数据,这显然不是我们想要的效果。
造成这个问题的原因是,我用keep-alive缓存了列表页,当我从首页进入的时候,还是用的缓存的数据。那我们可以动态去决定什么时候缓存列表页,什么时候不缓存吗?
方案:
通过keep-alive的include和exclude来决定缓存的组件,exclude表示不被缓存的组件名,inclde表示缓存的组件名,当两者同时存在时,exclude的优先级更高,所以我们通过动态获取exclude的值来实现动态缓存。那怎么动态修改exclude的值呢?通过路由的导航守卫router.beforeEach去判断当我们下一个路由是跳转到首页时,我们把exlude值修改成列表页的名字,其他情况都是空字符串。
Result
被keepalive包含的组件不会被再次初始化,也就意味着不会重走生命周期函数。但是我们可以通过
activated, deactivated 两个生命周期进行控制。而且也避免了重新加载数据。但是考虑某些情况我们需要它重新加载数据,所以我们需要动态决定组件是否需要缓存,通过keep-alive的include和exclude属性,并且利用include和exclude同时存在是,exclude的优先级更高,动态加载exclude,这个值在路由的导航守卫router.beforeEach中动态去修改。
思考:
因为之前的技术栈是react,所以每次我写vue的时候都会下意识思考如果是用react我会怎么去实现。
- 在 React 中,我们通常会使用路由去管理不同的页面,而在切换页面时,路由将会卸载掉未匹配的页面组件,所以上述列表页例子中,当用户从详情页退回列表页时,会回到列表页顶部,因为列表页组件被路由卸载后重建了,状态被丢失。
- 如果说按照之前的做法,我可能是onScroll事件监听滑动,或者是点击列表项的时候获取到容器的scrollTop,然后把scrollTop存到我们model里面,并且需要判断路由状态,如果是从详情页跳转的,则需要读取scrollTop,在componentDidMount中定位,如果是从其他页面,比如首页进入到列表页,就不需要重新定位
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。