Vxe UI vue vxe-table vxe-grid 本身就支持虚拟滚动以及灵活的扩展,可也是由于太过灵活,可定制化程度太高,比如单元格自定义渲染,一旦写得不好,就会影响渲染卡顿。
vxe-table 和 vxe-grid
直接使用 vxe-grid,grid 的渲染性能是最优的,全部功能比 table 强非常多。
全局参数
对于不同项目,只需要利用好全局参数,基本上就可以实现无需封装了,基本上所有组件都支持全局参数配置。
整理了最优的全局参数配置:
import { VxeUI } from 'vxe-table'
VxeUI.setConfig({
version: 0, // 版本号,比如使用了相关的浏览器本地储存功能,当组件升级后系统自动重置数据,那么只需要不断增加版本即可
zIndex: 2000, // 弹出层的层级,一般设置 2000 就够了
table: {
border: true, // 表格边框
showOverflow: true, // 单元格溢出隐藏,大幅提高渲染性能
autoResize: true, // 自动根据父容器适应调整宽高
columnConfig: {
resizable: true // 列宽拖动调整宽度
},
editConfig: {
trigger: 'click' // 默认点击编辑
},
sortConfig: {
trigger: 'cell' // 默认单元格编辑
},
scrollX: {
enabled: false, // 默认关闭横向虚拟滚动,一般横向使用很少,局部开启就可以
gt: 15
},
scrollY: {
enabled: true, // 默认启用纵向虚拟滚动
gt: 20
}
},
// grid 会默认继承 table,所以无需重复设置一样的参数
grid: {
toolbarConfig: {
custom: true // 全局开启自定义列
},
// 数据代理相关配置,无使用就忽略
proxyConfig: {
showResponseMsg: false,
showActiveMsg: true,
response: {
total: 'page.total',
result: 'data',
list: 'data'
}
}
}
})
使用
高度是非常重要的参数,可以固定,也可以设置 100% 自适应父容器,提高渲染性能
一千行渲染
一万行渲染
十万行渲染
<template>
<div>
<p>
<vxe-button @click="loadList(1000)">1k行</vxe-button>
<vxe-button @click="loadList(5000)">5k行</vxe-button>
<vxe-button @click="loadList(10000)">1w行</vxe-button>
<vxe-button @click="loadList(50000)">5w行</vxe-button>
<vxe-button @click="loadList(100000)">10w行</vxe-button>
</p>
<vxe-grid ref="gridRef" v-bind="gridOptions"></vxe-grid>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { VxeUI } from 'vxe-table'
const gridRef = ref()
const gridOptions = reactive({
border: true,
showOverflow: true,
height: 800,
loading: false,
scrollY: {
enabled: true,
gt: 0
},
columns: [
{ type: 'seq', title: '序号', width: 100 },
{ field: 'name', title: 'Name', minWidth: 180 },
{ field: 'role', title: 'Role', width: 200 },
{ field: 'num', title: 'Num', width: 200 },
{ field: 'address', title: 'Address', width: 200 }
],
data: []
})
// 模拟行数据
const loadList = (size = 200) => {
gridOptions.loading = true
setTimeout(() => {
const dataList = []
for (let i = 0; i < size; i++) {
dataList.push({
id: i,
name: `名称${i} 名称名称 称`,
role: `role ${i}`,
num: 20,
address: 'shenzhen shen'
})
}
const $grid = gridRef.value
if ($grid) {
const startTime = Date.now()
$grid.loadData(dataList).then(() => {
VxeUI.modal.message({
content: `加载时间 ${Date.now() - startTime} 毫秒`,
status: 'success'
})
gridOptions.loading = false
})
}
}, 350)
}
loadList(500)
</script>
可编辑
当列比较多时,就可以开启横向虚拟滚动,以下同时使用可编辑+纵向和横向虚拟滚动,极致流畅的渲染表格,同时还能支持任意扩展,单元格任意自定义(需要注意自定义的逻辑是否复杂,过度复杂会影响渲染性能)。
10000行 x 150列
滚动以及编辑输入都是丝滑流畅的
<template>
<div>
<p>
<vxe-button @click="loadDataAndColumns(100, 50)">100行50列</vxe-button>
<vxe-button @click="loadDataAndColumns(1000, 80)">1k行80列</vxe-button>
<vxe-button @click="loadDataAndColumns(5000, 100)">5k行100列</vxe-button>
<vxe-button @click="loadDataAndColumns(10000, 150)">1w行150列</vxe-button>
<vxe-button @click="loadDataAndColumns(30000, 200)">3w行200列</vxe-button>
</p>
<p>
<vxe-button @click="loadDataAndColumns(50, 50)">50行100列</vxe-button>
<vxe-button @click="loadDataAndColumns(80, 1000)">80行1k列</vxe-button>
<vxe-button @click="loadDataAndColumns(100, 5000)">100行5k列</vxe-button>
<vxe-button @click="loadDataAndColumns(150, 10000)">200行1w列</vxe-button>
<vxe-button @click="loadDataAndColumns(200, 30000)">200行3w列</vxe-button>
</p>
<vxe-grid ref="gridRef" v-bind="gridOptions"></vxe-grid>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { VxeUI } from 'vxe-table'
const gridRef = ref()
const gridOptions = reactive({
border: true,
loading: false,
showOverflow: true,
showHeaderOverflow: true,
showFooterOverflow: true,
height: 800,
editConfig: {
trigger: 'click',
mode: 'cell'
},
scrollY: {
enabled: true,
gt: 0
},
scrollX: {
enabled: true,
gt: 0
}
})
// 模拟行与列数据
const loadDataAndColumns = (rowSize, colSize) => {
gridOptions.loading = true
setTimeout(() => {
const $grid = gridRef.value
const colList = []
for (let i = 0; i < colSize; i++) {
colList.push({
field: `col${i}`,
title: `标题${i}`,
width: 160,
editRender: { name: 'VxeInput' }
})
}
const dataList = []
for (let i = 0; i < rowSize; i++) {
const item = {
id: 10000 + i
}
for (let j = 0; j < colList.length; j++) {
item[`col${j}`] = `值_${i}_${j}`
}
dataList.push(item)
}
if ($grid) {
const startTime = Date.now()
$grid.loadColumn(colList).then(() => {
return $grid.loadData(dataList)
}).then(() => {
VxeUI.modal.message({
content: `加载时间 ${Date.now() - startTime} 毫秒`,
status: 'success'
})
gridOptions.loading = false
})
}
}, 50)
}
onMounted(() => {
loadDataAndColumns(50, 50)
})
</script>
更复杂的渲染
使用左右冻结列+复杂的单元格渲染、头像、上传图片、全屏预览,开关组件等
以下测试渲染1000-50000行速度
<template>
<div>
<vxe-button @click="loadData(5000)">加载5k条</vxe-button>
<vxe-button @click="loadData(10000)">加载1w条</vxe-button>
<vxe-button @click="loadData(50000)">加载5w条</vxe-button>
<vxe-grid v-bind="gridOptions"></vxe-grid>
</div>
</template>
<script setup>
import { reactive, nextTick } from 'vue'
import { VxeUI } from 'vxe-table'
const flag1CellRender = reactive({
name: 'VxeSwitch'
})
const imgUrlCellRender = reactive({
name: 'VxeImage',
props: {
width: 36,
height: 36
}
})
const imgList1CellRender = reactive({
name: 'VxeUpload',
props: {
mode: 'image',
readonly: true,
moreConfig: {
maxCount: 2
},
imageStyle: {
width: 40,
height: 40
}
}
})
const gridOptions = reactive({
border: true,
showOverflow: true,
showHeaderOverflow: true,
showFooterOverflow: true,
loading: false,
height: 800,
columnConfig: {
resizable: true
},
scrollX: {
enabled: true,
gt: 0
},
scrollY: {
enabled: true,
gt: 0
},
columns: [
{ title: '列0', field: 'col0', width: 100, fixed: 'left' },
{ title: '列1', field: 'imgUrl', width: 80, fixed: 'left', cellRender: imgUrlCellRender },
{ title: '列2', field: 'col2', width: 90, fixed: 'left' },
{ title: '列3', field: 'col3', width: 200 },
{ title: '列4', field: 'col4', width: 140 },
{ title: '列5', field: 'col5', width: 300 },
{ title: '列6', field: 'col6', width: 160 },
{ title: '列7', field: 'col7', width: 120 },
{ title: '列8', field: 'col8', width: 400 },
{ title: '列9', field: 'col9', width: 160 },
{ title: '列10', field: 'col10', width: 160 },
{ title: '列11', field: 'col11', width: 180 },
{ title: '列12', field: 'col12', width: 160 },
{ title: '列13', field: 'col13', width: 80 },
{ title: '列14', field: 'col14', width: 120 },
{ title: '列15', field: 'col15', width: 360 },
{ title: '列16', field: 'col16', width: 150 },
{ title: '列17', field: 'col17', width: 380 },
{ title: '列18', field: 'col18', width: 100 },
{ title: '列19', field: 'col19', width: 290 },
{ title: '列20', field: 'col20', width: 80 },
{ title: '列21', field: 'col21', width: 100 },
{ title: '列22', field: 'col22', width: 120 },
{ title: '列23', field: 'col23', width: 270 },
{ title: '列24', field: 'col24', width: 330 },
{ title: '列25', field: 'col25', width: 460 },
{ title: '列26', field: 'col26', width: 280 },
{ title: '列27', field: 'col27', width: 220 },
{ title: '列28', field: 'col28', width: 120 },
{ title: '列29', field: 'col29', width: 180 },
{ title: '列30', field: 'col30', width: 500 },
{ title: '列31', field: 'col31', width: 600 },
{ title: '列32', field: 'col32', width: 100 },
{ title: '列33', field: 'col33', width: 490 },
{ title: '列34', field: 'col34', width: 100 },
{ title: '列35', field: 'col35', width: 150 },
{ title: '列36', field: 'col36', width: 800 },
{ title: '列37', field: 'col37', width: 400 },
{ title: '列38', field: 'col38', width: 800 },
{ title: '列39', field: 'col39', width: 360 },
{ title: '列40', field: 'col40', width: 420 },
{ title: '列41', field: 'col41', width: 100 },
{ title: '列42', field: 'col42', width: 120 },
{ title: '列43', field: 'col43', width: 280 },
{ title: '列44', field: 'col44', width: 170 },
{ title: '列45', field: 'col45', width: 370 },
{ title: '列46', field: 'col46', width: 420 },
{ title: '列47', field: 'col47', width: 170 },
{ title: '列48', field: 'col48', width: 400 },
{ title: '列49', field: 'col49', width: 220 },
{ title: '列50', field: 'col50', width: 170 },
{ title: '列51', field: 'col51', width: 160 },
{ title: '列52', field: 'col52', width: 500 },
{ title: '列53', field: 'col53', width: 280 },
{ title: '列54', field: 'col54', width: 170 },
{ title: '列55', field: 'col55', width: 370 },
{ title: '列56', field: 'col56', width: 120 },
{ title: '列57', field: 'col57', width: 170 },
{ title: '列58', field: 'col58', width: 400 },
{ title: '列59', field: 'col59', width: 220 },
{ title: '列60', field: 'col60', width: 650 },
{ title: '列61', field: 'col61', width: 600 },
{ title: '列62', field: 'col62', width: 100 },
{ title: '列63', field: 'col63', width: 490 },
{ title: '列64', field: 'col64', width: 100 },
{ title: '列65', field: 'col65', width: 150 },
{ title: '列66', field: 'col66', width: 800 },
{ title: '列67', field: 'col67', width: 400 },
{ title: '列68', field: 'col68', width: 800 },
{ title: '列69', field: 'col69', width: 360 },
{ title: '列70', field: 'col70', width: 650 },
{ title: '列71', field: 'col71', width: 600 },
{ title: '列72', field: 'col72', width: 100 },
{ title: '列73', field: 'col73', width: 490 },
{ title: '列74', field: 'col74', width: 100 },
{ title: '列75', field: 'col75', width: 150 },
{ title: '列76', field: 'col76', width: 800 },
{ title: '列77', field: 'col77', width: 400 },
{ title: '列78', field: 'col78', width: 800 },
{ title: '列79', field: 'col79', width: 360 },
{ title: '列80', field: 'col80', width: 650 },
{ title: '列81', field: 'col81', width: 600 },
{ title: '列82', field: 'col82', width: 100 },
{ title: '列83', field: 'col83', width: 490 },
{ title: '列84', field: 'col84', width: 100 },
{ title: '列85', field: 'col85', width: 150 },
{ title: '列86', field: 'col86', width: 800 },
{ title: '列87', field: 'col87', width: 400 },
{ title: '列88', field: 'col88', width: 800 },
{ title: '列89', field: 'col89', width: 360 },
{ title: '列90', field: 'col90', width: 650 },
{ title: '列91', field: 'col91', width: 600 },
{ title: '列92', field: 'col92', width: 100 },
{ title: '列93', field: 'col93', width: 490 },
{ title: '列94', field: 'col94', width: 100 },
{ title: '列95', field: 'col95', width: 150 },
{ title: '列96', field: 'col96', width: 800 },
{ title: '列97', field: 'col97', width: 400 },
{ title: '列98', field: 'col98', width: 70, fixed: 'right' },
{ title: '列99', field: 'imgList1', width: 120, fixed: 'right', cellRender: imgList1CellRender },
{ title: '列100', field: 'flag1', width: 100, fixed: 'right', cellRender: flag1CellRender }
],
data: []
})
// 模拟行数据
const loadData = (rowSize) => {
const dataList = []
for (let i = 0; i < rowSize; i++) {
const item = {
id: 10000 + i,
imgUrl: i % 3 === 0 ? 'https://vxeui.com/resource/img/546.gif' : 'https://vxeui.com/resource/img/673.gif',
imgList1: i % 4 === 0
? [
{ name: 'fj577.jpg', url: 'https://vxeui.com/resource/img/fj577.jpg' }
]
: [
{ name: 'fj573.jpeg', url: 'https://vxeui.com/resource/img/fj573.jpeg' },
{ name: 'fj562.png', url: 'https://vxeui.com/resource/img/fj562.png' }
],
flag1: i % 5 === 0
}
for (let j = 0; j < 120; j++) {
item[`col${j}`] = `值_${i}_${j}`
}
dataList.push(item)
}
gridOptions.loading = true
setTimeout(() => {
const startTime = Date.now()
gridOptions.data = dataList
gridOptions.loading = false
nextTick(() => {
VxeUI.modal.message({
content: `加载时间 ${Date.now() - startTime} 毫秒`,
status: 'success'
})
})
}, 100)
}
loadData(200)
</script>
以上复杂度还不是很高,当渲染复杂度更大是,鼠标滚轮的速度快于渲染速度,这是就会出现渲染期空白。
可以通过设置参数 scroll-y.mode = ‘wheel’ 解决,虚拟滚动操作过快渲染期空白问题
// ...
scrollY: {
enabled: true,
gt: 0,
mode: 'wheel'
},
// ...
滚动操作是非常流畅的。