vue树形多列_基于element-uiel-table开发虚拟列表(树形列
表)
前⾔
基于之前⽀持的el-table开发完成后,在数据量过⼤的时候,会出现渲染慢,表格卡顿等致命问题,⽽element-ui的el-table本⾝没有像antd⼀样提供虚拟列表的demo和相关⽀持,因此本⽂在上次的开发基础上,继⽽开展虚拟列表的开发。本次分为普通列表和树形列表两种,树形在普通列表上⾯多了⼀些情况考虑,例如展开收缩等。
虚拟列表
虚拟列表简单概述就是滚动分页,通过有限的视⼝来切⽚⼤量的,因为相⽐于js运算,渲染是⼀个很慢的过程,因此通过⼀定的js计算,保证更少的数据渲染,通常可以获得更好的⽤户体验。⼀般虚拟列表可以通过上下的动态padding值,来是滚动区域⼀直显⽰当前切⽚出的数据,以及通过transform的⽅法来动态移动可视区。transform这种⽅法理论上性能要好⼀些,因为浏览器渲染本⾝是分图层渲染的,⽽transform操作的视图,会被浏览器单独分层出来,渲染性能更优。
下图两种⽅式:
本次el- 上的虚拟列表,采⽤了padding的⽅案,原因是transform 会使el-table的样式混乱,如果是⾃⼰开发的table或者其他魔改⽀持度较好的插件的话优先transform。
普通列表
⾸先看⼀下el-table 渲染300 条的速度。
本次的测试代码有300条,本⾝并不多,但是有8列都是插槽中渲染的表单组件,因此渲染速度要慢很多,时间花销6s+。(antd 的table渲染要快⼀些,后⾯说原因)
增加虚拟列表后渲染速度:
开发流程
step1
计算总⾼度
height = list.length * 65
// height 为列表实际总⾼度
// 65 为每⼀⾏的⾏⾼,根据实际修改
// list为实际数据长度
step2
计算上下padding值
paddingTop = scrollTop + "px";
paddingBottom = height - 10 * 65 - scrollTop + "px";
// scrollTop 为滚动的⾼度,即列表向下滚动的距离
// height 总⾼度
// 10为实际渲染的条数
step3
监听列表滚动,动态为列表设置padding等样式。
mounted() {undefined
console.time("render300条时间:");
ws = new Array(300).fill(0).map((v, i) => ({undefined
name: i,
children: []
}));
ws = [...ws];
this.setIndex(ws);
this.calcList();
this.$nextTick(() => {undefined
this.debounceFn = _.debounce(() => {undefined
this.scrollTop = this.$refs.table.bodyWrapper.scrollTop;
}, 100);
this.$refs.table.bodyWrapper.addEventListener("scroll", this.debounceFn);
});
this.$nextTick(() => {undefined
console.timeEnd("render300条时间:");
});
},
监听的⽬标是这个: this.$refs.table.bodyWrapper,防抖的时间设置为100。
step4
数组切⽚,渲染虚拟列表。
this.startIndex = Math.floor(scrollTop / 65);
this.virtualRows = ws.slice(
this.startIndex,
this.startIndex + 10
);
根据滚动位置计算数组切⽚的起始点,然后截取相应的list渲染。
⽀持列fixed(Table-column Attributes - fixed)
上⾯说到el-table要⽐antd的table渲染更慢,其中⼀条原因我个⼈认为是,el-table 在⽀持左右固定列的时候会克隆⼀份table,然后按照层级关系,使得UI上看到左右列的固定。如果左右都设置了fixed,就会有三个table同时在页⾯上。
⽽ Antd的table组件在左右fixed时就不会有这个问题,因此本⼈亲测在300条相同数据的情境下,Antd的性能要好不少。⾔归正传,要解决fixed的问题,就是要把这三个table的padding都去设置⼀遍才⾏,否则就会出现部分区域没有被顶下来⽽错位的情况。
let mainTable = this.$refs.table.$el.getElementsByClassName(
"el-table__body"
);
Array.from(mainTable).forEach(v => {undefined
v.style.height = height + "px";
if (this.startIndex + 10 >= this.num) {undefined
// 由于el-table 在滚动到最后时,会出现抖动,因此增加判断,单独设置属性
v.style.paddingTop = scrollTop - 65 + "px";
v.style.paddingBottom = 0;
} else {undefined
v.style.paddingTop = scrollTop + "px";
v.style.paddingBottom = height - 10 * 65 - scrollTop + "px";
}
});
到当前table下的所有内容区域,遍历设置样式属性。
树形列表
树形列表由于多⼀步展开折叠的操作,以及本⾝数据结构的原因,数据预处理要复杂⼀下,不能直接slice,⽽要计算出相应区间然后⽣成新的数组。其次,在被收缩的⼦项是不渲染到table当中的,因此,要把被收缩的项排除在外。除了普通列表的⼏个step之外,树形列表还需有以下操作。
数组切⽚
通过滚动计算出的起始点,以及可视区域的列表长度,可以得到⼀个区间,如【3,11】,即通过深度优先遍历(也是树形列表排列的顺序),到第3到11条数据(不包含被折叠项),然后赋值到新的数组。
clacTree() {undefined
let count = 0;
this.virtualRows = [];
this.listLen = 0;
const fn = arr => {undefined
for (let i = 0; i < arr.length; i++) {undefined
count++;
this.listLen++;
if (count >= this.startIndex && count <= this.startIndex + 10) {undefined
thisbineArr(_.cloneDeep(arr[i]));
}
arr[i].children && arr[i].expended === "true" && fn(arr[i].children);
}
fn(ws);
},
combineArr(node) {undefined
let flag = false;
node.children = [];
const fn = arr => {undefined
arr.forEach(v => {undefined
if (node.pid === v.customIndex) {undefined
v.children.push(node);
flag = true;
}
v.children && fn(v.children);
});
};
fn(this.virtualRows);
if (!flag) {undefined
this.virtualRows.push(node);
}
},
这⾥只对展开项进⾏操作,未展开的不去遍历和渲染,总⾼度也不计⼊。新数组赋值的时候,我通过⼆次遍历新的数组,再根据pid去push 到相应位置,这种做法是因为实际业务需要,⼆次遍历中还有部分属性需要保持引⽤,以及部分属性是不可枚举的,深拷贝会丢失,如果只是截取树的⼀部分形成
新的树,可以根据初始化得到的path属性,然后利⽤lodash的_.set 来完成。
展开收缩vue element admin
el-table 的 expand-row-keys 传⼊⼀个数组,为默认的展开项,之后每次渲染都参考这个数组来决定列表是否展开,这个属性不能⾃动在展开收缩的时候把设置的 row-key 推⼊推出,⽽要⼿动的计算。在@expand-change事件中,来操作数组,以及判断被收缩的项有没有⼦集,如果有⼦集要给⼀个标记位,来为之后的列表渲染做准备。
expendRow(rows, expended) {undefined
// const
this.DFS_Array(ws, v => {undefined
if (v.customIndex == rows.customIndex) {undefined
v.hasChild =
}
if (!expended) {undefined
} else {undefined
}
this.calcList(this.scrollTop);
},
DFS_Array(arr, fn) {undefined
for (let i = 0; i < arr.length; i++) {undefined
fn(arr[i]);
if (arr[i].children && arr[i].children.length > 0) {undefined
this.DFS_Array(arr[i].children, fn);
}
}
}
在收缩之后由于把列表中的children整个移除,所以在el-table上⾯的展开箭头就不能正常显⽰了,因为在渲染数据中并没有⼦节点,⽽实际数据中⼜是有⼦集的,所以,在上⾯增加的hasChild属性,就起到这个作⽤,他标记了数据被折叠,且有⼦集可展开的情况。因此,需要在列表中主动把展开的箭头加⼀下。
prop="customIndex"
fixed
label="序号"
sortable
width="180"
v-slot="{$index, row}"
>
>
{undefined{row.customIndex}}
⾄此 树形列表的虚拟列表也整合完毕了。本次⽰例代码很多地⽅⽐较仓促,待优化情景较多,除了拼凑新的树那⾥,还有滚动的缓存,如果树⽐较⼤的话,js的计算时间也要考虑⼊内,还有渲染的虚拟列表应该不从本⾝的第⼀位开始进⼊视⼝,这样的话,在⼀定范围的向上向下滚动,就可以⼀定程度的减少⽩屏。
总结
虚拟列表通过减少实际渲染数据来优化性能,在不对-ui做较⼤改动的情况下,满⾜了⼤量数据,包括树形的结构数据的渲染场景。如果考虑之前的列表的表单验证的情景,需要让部分属性脱离引⽤,如children,否则会污染源数据,其次让表单数据保持引⽤关联,这样就不必专门给表单组件设置事件,来匹配源数据的改动,即直接将新的列表的item的表单对象等于⽼的相应表单对象即可。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。