在开发项目的过程中遇到需求:表格渲染大量的数据,要求不使用分页。并且要运行在硬件配置相对较低及浏览器版本也较低的政府相关部门中。

结果:正常使用蚂蚁金服的Table组件渲染的话,在低配置电脑和低版本浏览器中渲染非常卡顿,甚至导致页面卡死。于是需要进行优化。高配置一点的电脑,渲染不会卡死,但也会有五六秒左右的渲染时间。

思路:

  1. 保证Table组件中渲染的数据,永远只有20条(可以任意其他条数都行)。当滚动条滚动时,则渲染不同的20条数据。

  2. 在表格DOM结构中append一个tr节点,设置高度,用于撑开表格结构,使渲染20条数据的滚动条高度看起来和渲染所有数据的滚动条高度一样(随着滚动条滚动,这个tr节点的高度要随之减少)

  3. 同样为了视觉效果,需要设置表格DOM结构的transform: translateY属性,使得表格滚动时,其上部的高度也能撑开

实现方法

  • Vue中可以使用computed计算属性,对所有的数据进行过滤,只取20条。
  • 监听表格的滚动事件,在事件中改变当前的20条数据,同时改变tr节点的高度以及表格的transform: translateY属性

以下是我抽离的一个简单组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
<template>
<div ref="tableWrap">
<a-table
ref="antTableRef"
:rowKey="rowKey"
:dataSource="filteredData"
:columns="tableColumns"
:pagination="false"
:scroll="scroll"
>
<template
v-for="slotItem of slots"
:slot="slotItem.name"
slot-scope="text, record, index, column"
>
<template v-if="'customTitleCheck' === slotItem.name">
<a-checkbox
:key="slotItem.name"
:disabled="disabledAll"
:checked="checkedAll"
:indeterminate="indeterminateAll"
@change="handleCheckChangeAll"
></a-checkbox>
</template>
<template v-else-if="'customTableCheck' === slotItem.name">
<a-checkbox
:key="slotItem.name"
:checked="selectedRowKeys.includes(record[rowKey])"
:disabled="handleCheckDisabled(record)"
@change="e => handleCheckChange(e, record)"
></a-checkbox>
</template>
<slot v-else :name="slotItem.name" v-bind="{ text, record, index, column }"></slot>
</template>
</a-table>
</div>
</template>

<script>
import { Table } from 'ant-design-vue'
export default {
name: 'TableRender',
// 扩展ant的Table组件
extends: Table,
props: {
rowKey: {
type: [String, Function],
default() {
return 'id'
}
},
// 表头数据
columns: {
type: Array,
default() {
return []
}
},
// 是否显示选择框
showChecked: {
type: Boolean,
default() {
return true
}
},
defaultCheckedKeys: {
type: Array,
default() {
return []
}
},
// 复选框禁用方法
disbleCheckedFunc: {
type: [Function, Boolean],
default() {
return () => {
return false
}
}
},
scroll: {
type: Object,
default() {
return {}
}
},
tableDatas: {
type: Array,
default() {
return []
}
},
// 显示条数,默认显示20条,只渲染20个trDOM
showNum: {
type: Number,
default() {
return 20
}
},
// 溢出显示条数的显示数量
spillDataNum: {
type: Number,
default() {
return 20
}
}
},
data() {
return {
tableData: [],
currentStartIndex: 0,
currentEndIndex: this.showNum,
selectedRowKeys: this.defaultCheckedKeys,
disabledAll: false,
checkedAll: false,
canChooseLength: 0,
tableWrapRef: '',
showRowNum: 0,
rowHeight: 40,
selectTbody: '',
createElementTR: '',
createElementTRHeight: 0
}
},
watch: {
tableDatas() {
this.initScrollDOM()
// 设置全选的禁用
this.disabledAll = true
this.tableDatas.forEach(item => {
if (!this.handleCheckDisabled(item)) {
this.canChooseLength++
this.disabledAll = false
return
}
})
}
},
computed: {
filteredData() {
let list = this.tableDatas.filter((item, index) => {
if (index < this.currentStartIndex) {
return false
} else if (index > this.currentEndIndex) {
return false
} else {
return true
}
})
return list
},
tableColumns() {
let columns = this.columns
if (!this.showChecked) {
return columns
} else {
columns.unshift({
width: 70,
scopedSlots: {
title: 'customTitleCheck',
customRender: 'customTableCheck'
}
})
return columns
}
},
// 插槽
slots() {
const slots = []
for (const column of this.columns) {
const conf = {}
for (const k in column) {
if (k !== 'scopedSlots') conf[k] = column[k]
}
if (column.scopedSlots && column.scopedSlots.customRender) {
slots.push({
name: column.scopedSlots.customRender,
conf: conf
})
}
if (column.scopedSlots && column.scopedSlots.title) {
slots.push({
name: column.scopedSlots.title,
conf: conf
})
}
if (column.children) {
const childrens = column.children
childrens.map(child => {
if (child.scopedSlots) {
childrens.valueKey = child.key
slots.push({
name: `${child.scopedSlots.customRender}`,
conf: child
})
}
})
}
}
return slots
},
selectedRow() {
const rows = []
const selectedLength = this.selectedRowKeys.length
for (const table of this.tableDatas) {
if (this.selectedRowKeys.includes(table[this.rowKey])) {
rows.push(table)
}
if (rows.length >= selectedLength) {
break
}
}
return rows
},
indeterminateAll() {
const length = this.selectedRowKeys.length
return length > 0 && length < this.canChooseLength
}
},
methods: {
initScrollDOM() {
this.$nextTick(() => {
this.tableRef = this.$refs.antTableRef
const el = this.tableRef.$el
const dataSize = this.tableDatas.length
const selectWrap = el.querySelector('.ant-table-body')
this.selectTbody = selectWrap.querySelector('table tbody')
const selectRow = selectWrap.querySelector('table tr')
if (!selectRow) {
return
}
this.rowHeight = selectRow.clientHeight
this.showRowNum = Math.round(selectWrap.clientHeight / this.rowHeight)

this.createElementTR = document.createElement('tr')
let createElementTRHeight =
(dataSize - this.showRowNum - this.spillDataNum) * this.rowHeight
this.createElementTR.setAttribute('style', `height: ${createElementTRHeight}px;`)
this.selectTbody.append(this.createElementTR)

selectWrap.addEventListener('scroll', e => {
const target = e.target
let topPx = target.scrollTop - this.spillDataNum * this.rowHeight
let topNum = Math.round(topPx / this.rowHeight)
let minTopNum = dataSize - this.spillDataNum - this.showRowNum
if (topNum > minTopNum) {
topNum = minTopNum
}
if (topNum < 0) {
topNum = 0
topPx = 0
}
this.selectTbody.setAttribute('style', `transform: translateY(${topPx}px)`)
this.createElementTR.setAttribute(
'style',
`height: ${createElementTRHeight - topPx > 0 ? createElementTRHeight - topPx : 0}px;`
)
this.currentStartIndex = topNum
this.currentEndIndex = topNum + this.showRowNum + this.spillDataNum
})
})
},
handleCheckDisabled(record) {
return this.disbleCheckedFunc(record)
},
handleCheckChange(e, item) {
const checked = e.target.checked
if (checked) {
if (this.selectedRowKeys.includes(item[this.rowKey])) return
this.selectedRowKeys.push(item[this.rowKey])
item.checked = true
} else {
const keyIndex = this.selectedRowKeys.indexOf(item[this.rowKey])
this.selectedRowKeys.splice(keyIndex, 1)
item.checked = false
}
this.$emit('handleTableCheck', this.selectedRowKeys, this.selectedRow)
},
handleCheckChangeAll(e) {
const checked = e.target.checked
if (checked) {
this.checkedAll = true
for (const data of this.tableDatas) {
if (
!this.handleCheckDisabled(data) &&
!this.selectedRowKeys.includes(data[this.rowKey])
) {
this.selectedRowKeys.push(data[this.rowKey])
}
}
} else {
this.checkedAll = false
this.selectedRowKeys = []
}
this.$emit('handleCheckChangeAll', checked, this.selectedRowKeys, this.selectedRow)
}
}
}
</script>

<style lang="scss"></style>

以下是该组件的使用组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
<template>
<div>
<TableRender
:columns="columns"
:tableDatas="tableDatas"
:showChecked="true"
:scroll="{ y: 400 }"
:defaultCheckedKeys="defaultCheckedKeys"
:disbleCheckedFunc="disbleCheckedFunc"
@handleTableCheck="handleTableCheck"
@handleCheckChangeAll="handleCheckChangeAll"
>
<template slot="operate">
<a-button type="primary">查看</a-button>
<a-button type="danger">删除</a-button>
</template>
</TableRender>
</div>
</template>

<script>
import TableRender from '../components/TableRender.vue'
export default {
components: {
TableRender
},
data() {
return {
tableDatas: [],
currentStartIndex: 0,
currentEndIndex: 20,
columns: [
{
title: '序号',
dataIndex: 'id',
key: 'id'
},
{
title: '姓名',
dataIndex: 'name',
key: 'name'
},
{
title: '年龄',
dataIndex: 'age',
key: 'age'
},
{
title: '性别',
dataIndex: 'sex',
key: 'sex'
},
{
title: '描述',
dataIndex: 'description',
key: 'description',
width: 280
},
{
title: '操作',
scopedSlots: {
customRender: 'operate'
}
}
],
defaultCheckedKeys: [4, 8]
}
},
created() {
this.getTableData()
},
methods: {
disbleCheckedFunc(record) {
return record.id % 4 === 0
},
getTableData() {
let cont = 0
let tableDatas = []
while (cont < 10000) {
cont = cont + 1
let object = {
name: '张三丰' + cont,
age: cont,
sex: cont % 2 === 0 ? '男' : '女',
description: '纸上得来终觉浅,绝知此事要躬行',
id: cont
}
tableDatas.push(object)
}
setTimeout(() => {
this.tableDatas = tableDatas
}, 0)
},
handleTableCheck(selectedRowKeys, selectedRow) {
console.log('选中的key:', selectedRowKeys)
console.log('选中的对象:', selectedRow)
},
handleCheckChangeAll(checked, selectedRowKeys, selectedRow) {
console.log(checked)
console.log('选中的key:', selectedRowKeys)
console.log('选中的对象:', selectedRow)
}
}
}
</script>

<style lang="scss"></style>

结果展示

可在以下地址查看效果:

http://hehuang.site/Components/#/

参考博客:

https://www.cnblogs.com/wiliam/p/14991509.html