vxe-table 实战:金融、监控实时刷新与高频数据更新优化
在金融、监控等场景中,表格数据需要高频刷新(如每秒更新一次股票价格)。若采用全量替换 data 的方式,会导致表格整体重绘,造成性能浪费和滚动位置丢失。
vxe-table 提供了高效的局部更新能力:我们只需修改数据源中对应行的属性,表格会自动触发视图更新,从而实现流畅的实时刷新。
本文将演示如何基于 vxe-grid 模拟股票行情数据,并实现每秒更新一次、同时保持滚动位置与选中状态的实时表格。
概览
| 关键点 | 解决方案 |
|---|---|
| 高频数据更新 | 使用 Object.assign 修改现有行对象,避免替换整个数组 |
| 保持表格状态 | 不重新赋值 data,只修改行内字段,表格不会丢失滚动条和选中行 |
| 性能优化 | 开启虚拟滚动 (virtualXConfig / virtualYConfig) 支持大量数据 |
| 定时器管理 | 组件卸载时 (onUnmounted) 清除定时器,避免内存泄漏 |
| 模拟真实数据 | 使用 XEUtils.random 生成随机股票指标,包括涨跌、成交量、量比等 |
代码
实现了每秒刷新一次真实行情数据的表格。

<template>
<div class="demo-page-wrapper">
<vxe-grid v-bind="gridOptions"></vxe-grid>
</div>
</template>
<script setup>
import { reactive, onUnmounted } from 'vue'
import XEUtils from 'xe-utils'
const formatAmount = ({ cellValue }) => {
if (cellValue) {
return XEUtils.commafy(cellValue, { digits: 2 })
}
return ''
}
const formatWanAmount = ({ cellValue }) => {
if (cellValue) {
return `${XEUtils.floor(cellValue / 10000, 2)}万`
}
return ''
}
const gridOptions = reactive({
border: true,
loading: false,
stripe: true,
showOverflow: true,
height: '100%',
columnConfig: {
drag: true,
resizable: true
},
columnDragConfig: {
trigger: 'cell',
showIcon: false,
showGuidesStatus: true
},
rowConfig: {
isHover: true
},
toolbarConfig: {
custom: true,
zoom: true
},
checkboxConfig: {
range: true
},
cellStyle({ column, row }) {
const cellValue = row[column.field]
switch (column.field) {
case 'increaseRatio':
case 'udRatio':
case 'expansionRatio':
case 'netInflow':
case 'theCommittee':
return {
color: cellValue < 0 ? 'green' : 'red'
}
case 'volumeRatio':
return {
color: cellValue < 1 ? 'green' : 'red'
}
case 'maxNum':
return {
color: row.increaseRatio < 0 ? 'green' : 'red'
}
case 'minNum':
return {
color: row.expansionRatio < 0 ? 'green' : 'red'
}
}
},
mouseConfig: {
selected: true
},
keyboardConfig: {
isEdit: true,
isArrow: true,
isEnter: true,
isBack: true,
isDel: true,
isEsc: true
},
virtualXConfig: {
gt: 0,
enabled: true
},
virtualYConfig: {
gt: 0,
enabled: true
},
columns: [
{ field: 'name', title: '名称', fixed: 'left', width: 200 },
{ field: 'currentPrice', title: '现价', minWidth: 100, formatter: formatAmount },
{ field: 'increaseRatio', title: '涨幅', minWidth: 100 },
{ field: 'udRatio', title: '涨跌', minWidth: 100 },
{ field: 'expansionRatio', title: '涨速', minWidth: 100 },
{ field: 'transactionQuantity', title: '成交量', minWidth: 100, formatter: formatWanAmount },
{ field: 'netInflow', title: '净流入', minWidth: 100, formatter: formatWanAmount },
{ field: 'maxNum', title: '最高', minWidth: 100 },
{ field: 'minNum', title: '最低', minWidth: 100 },
{ field: 'theCommittee', title: '委比', minWidth: 100 },
{ field: 'volumeRatio', title: '量比', minWidth: 100 },
{ field: 'turnoverRate', title: '换手率', minWidth: 100 },
{ field: 'priceEarningsRatio', title: '市盈率', minWidth: 100 },
{ field: 'totalMarketValue', title: '总市值', minWidth: 100, formatter: formatWanAmount },
{
field: 'circulatingMarketValue',
title: '流通市值',
minWidth: 100,
formatter: formatWanAmount
}
],
data: []
})
const neList = XEUtils.shuffle(['最牛科技', '老六软件', '智能科技', '非凡科技', '发展软件', '超级元宇宙', '航天概念', '短视频概念', '东方科技', '中华科技股', '中国最强概念股', '消费概念', '农业概念', '东方智能', '传播科技', '智慧企业', '智慧科技', '智能汽车', '区块链'])
const cacheList: RowVO[] = []
const loadMockData = (rSize) => {
gridOptions.loading = true
setTimeout(() => {
for (let i = cacheList.length; i < rSize; i++) {
const item = {
id: 1000000 + i,
name: neList[i % neList.length],
currentPrice: 0,
increaseRatio: 0,
udRatio: 0,
expansionRatio: 0,
transactionQuantity: 0,
netInflow: 0,
maxNum: 0,
minNum: 0,
theCommittee: 0,
volumeRatio: 0,
turnoverRate: 0,
priceEarningsRatio: 0,
totalMarketValue: 0,
circulatingMarketValue: 0
}
cacheList.push(item)
}
gridOptions.data = cacheList.slice(0, rSize)
gridOptions.loading = false
startUpdateData()
}, 150)
}
let isStop = false
let udTime
const stopUpdateData = () => {
isStop = true
if (udTime !== undefined) {
clearTimeout(udTime)
}
}
const startUpdateData = () => {
if (isStop) {
clearTimeout(udTime)
udTime = undefined
return
}
gridOptions.data.forEach((row) => {
Object.assign(row, {
currentPrice: XEUtils.random(99999, 999999),
increaseRatio: (XEUtils.random(1, 999) / 100) * (XEUtils.random(0, 1) ? 1 : -1),
udRatio: (XEUtils.random(1, 99) / 100) * (XEUtils.random(0, 1) ? 1 : -1),
expansionRatio: (XEUtils.random(1, 99) / 100) * (XEUtils.random(0, 1) ? 1 : -1),
transactionQuantity: XEUtils.random(899999, 999999),
netInflow: XEUtils.random(1, 9999) * (XEUtils.random(0, 1) ? 1 : -1),
maxNum: XEUtils.random(899999, 999999),
minNum: XEUtils.random(99999, 109999),
theCommittee: XEUtils.random(1, 99) * (XEUtils.random(0, 1) ? 1 : -1),
volumeRatio: XEUtils.random(1, 20) / 10,
turnoverRate: XEUtils.random(1, 50) / 10,
priceEarningsRatio: XEUtils.random(99, 99999) * (XEUtils.random(0, 1) ? 1 : -1),
totalMarketValue: XEUtils.random(999999, 9999999),
circulatingMarketValue: XEUtils.random(999999, 9999999)
})
})
udTime = setTimeout(() => {
startUpdateData()
}, 1000)
}
onUnmounted(() => {
stopUpdateData()
})
loadMockData(100)
</script>
性能优化
为什么不重新赋值 gridOptions.data?
如果采用:
gridOptions.data = newDataArray // ❌ 会触发全量重绘
表格会丢弃原有 DOM 和滚动状态,导致视觉闪烁、选中丢失。
而采用:
gridOptions.data.forEach(row => {
Object.assign(row, newValues) // ✅ 只修改已有对象的属性
})
Vue 能检测到对象属性的变化,从而仅重新渲染变化的单元格,性能极高且状态保持。
定时器模式
- 使用 setTimeout 递归调用实现周期性刷新(比 setInterval 更安全,避免回调堆积)。
- 通过 isStop 标志和 onUnmounted 钩子确保组件销毁时停止所有定时器。
虚拟滚动配置
virtualXConfig: { gt: 0, enabled: true },
virtualYConfig: { gt: 0, enabled: true }
动态样式与格式化
- cellStyle:根据数值正负或与其他列的关联动态设置文字颜色(如涨幅为红涨绿跌)。
- formatter:对成交量、总市值等进行单位转换(万/亿)和千分位格式化,保持数据简洁。
控制刷新频率,实际业务中可根据数据源推送速率调整间隔(如 500ms、2s),避免过度频繁触发渲染。

浙公网安备 33010602011771号