2

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中定位,如果是从其他页面,比如首页进入到列表页,就不需要重新定位

Lingo
18 声望7 粉丝