树形列表翻页,后端: 搞不了搞不了~~
背景
记得几年前做了一个报告,报告里面加载的是用户的历年作品还有会员信息,然后按照年月倒序展示出来,其中历年作品都要将作品的封面展示出来。一开始这个报告到没啥问题,而且一个时间轴下来感觉挺好,有用户的作品、会员记录、关注以及粉丝记录很全面。直到最近忽然有一批用户说一进到这个报告页面就卡住不动了,上去一查发现不得了,都是铁杆用户,每年作品都几百个,导致几年下来,这个报告返回了几千个作品,包含上千的图片。
问题分析
上千的图片,肯定会卡,首先想到的是做图片懒加载。这个很简单,使用一个vue的全局指令就可以了。但是上线发现,没啥用,dom节点多的时候,懒加载也卡。
然后就问服务端能不能支持分页,服务端说数据太散,连表太多,树形结构很难做分页。光查询出来就已经很费劲了。
没办法于是想了一下如何前端来处理掉。
思路
由于是app中的嵌入页面,首先考虑通过滚动进行分页加载。
一次性拿了全部的数据,肯定不能直接全部渲染,我们可以只渲染一部分,比如第一个节点,或者前几个节点。
随着滚动一个节点一个节点或者一批一批的渲染到dom中。
实现
本文仅展示一种基于vue的实现
1. 容器
设计一个可以进行滚动翻页的容器 然后绑定滚动方法OnPageScrolling
<style lang="less" scoped>
.study-backup {
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
width: 100%;
height: 100%;
position: relative;
min-height: 100vh;
background: #f5f8fb;
box-sizing: border-box;
}
</style>
<template>
<section class="report" @scroll="OnPageScrolling($event)">
</section>
</template>
2.初始化数据
这里定义一下树形列表的数据结构,实现初始化渲染,可以渲染一个树节点,或者一个树节点的部分子节点
GetTreeData() {
treeapi
.GetTreeData({ ... })
.then((result) => {
// 处理结果
const data = Handle(result)
// 这里备份一份数据 不参与展示
this.backTreeList = data.map((item) => {
return {
id: item.id,
children: item.children
}
})
// 这里可以初始化为第一个树节点
const nextTree = this.backTreeList[0]
const nextTansformTree = nextTree.children.splice(0)
this.treeList = [{
id: nextTree.id,
children: nextTansformTree
}]
// 这里可以初始化为第一树节点 但是只渲染第一个子节点
const nextTree = this.backTreeList[0]
const nextTansformTree = nextTree.children.splice(0, 1)
this.treeList = [{
id: nextTree.id,
children: nextTansformTree
}]
})
},
3.滚动加载
这里通过不断的把 backTreeList 的子节点转存入 treeList来实现分页加载。
OnPageScrolling(event) {
const container = event.target
const scrollTop = container.scrollTop
const scrollHeight = container.scrollHeight
const clientHeight = container.clientHeight
// console.log(scrollTop, clientHeight, scrollHeight)
// 判断是否接近底部
if (scrollTop + clientHeight >= scrollHeight - 10) {
// 执行滚动到底部的操作
const currentReport = this.backTreeList[this.treeList.length - 1]
// 检测匹配的当前树节点 treeList的长度作为游标定位
if (currentReport) {
// 判断当前节点的子节点是否还存在 如果存在则转移到渲染树中
if (currentReport.children.length > 0) {
const transformMonth = currentReport.children.splice(0, 1)
this.treeList[this.treeList.length - 1].children.push(
transformMonth[0]
)
// 如果不存在 则寻找下一树节点进行复制 同时复制下一节点的第一个子节点 当然如果寻找不到下一树节点则终止翻页
} else if (this.treeList.length < this.backTreeList.length) {
const nextTree = this.backTreeList[this.treeList.length]
const nextTansformTree = nextTree.children.splice(0, 1)
this.treeList.push({
id: nextTree.id,
children: nextTansformTree
})
}
}
}
}
4. 逻辑细节
从上面代码可以看到,翻页的操作是树copy的操作,将备份树的子节点转移到渲染树中
copy备份树的第一个节点到渲染树,同时将备份树的第一个节点的子节点的第一个节点转移到渲染树的第一个节点的子节点中
所谓转移操作,就是数组splice操作,从一颗树中删除,然后把删除的内容插入到另一颗树中
由于渲染树是从长度1开始的,所以我们可以根据渲染树的长度作为游标和备份树进行匹配,设渲染树的长度为当前游标
根据当前游标查询备份树,如果备份树的当前游标节点的子节点不为空,则进行转移
如果备份树的当前游标节点的子节点为空,则查找备份树的当前游标节点的下一节点,设为下一树节点
如果找到了备份树的当前游标节点的下一节点,扩展渲染树,将下一树节点复制到渲染树,同时将下一树节点的子节点的第一节点复制到渲染树
循环4-6,将备份树完全转移到渲染树,完成所有翻页
扩展思路
这个方法可以进行封装,将每次复制的节点数目和每次复制的子节点数目作为传参,一次可复制多个节点,这里就不做展开
来源:juejin.cn/post/7270503053358612520