
AgGrid 的 applyTransactionAsync 在高频 WebSocket 数据流场景下易引发重复添加、数据覆盖等竞态问题;根本解法是将数据状态与网格组件分离,通过受控模式统一管理状态并批量更新。
aggrid 的 `applytransactionasync` 在高频 websocket 数据流场景下易引发重复添加、数据覆盖等竞态问题;根本解法是将数据状态与网格组件分离,通过受控模式统一管理状态并批量更新。
在实时数据驱动的应用中(如金融行情、IoT 监控面板),开发者常依赖 AgGrid 的 applyTransactionAsync 实现“异步提交 + 批量渲染”,以兼顾性能与响应性。然而,正如实际使用中暴露的问题所示:当多个 WebSocket 消息在前一事务尚未完成时连续到达,且均尝试 add 同一 rowId(如 "X")时,AgGrid 无法自动去重或合并操作——它会按接收顺序依次执行事务,导致重复行插入、控制台报错(duplicate row id),以及关键列数据被后到消息意外覆盖(因 update 是全量替换而非增量合并)。
这并非 API Bug,而是设计使然:applyTransactionAsync 本质是非阻塞、无序、无状态合并能力的底层操作,它不感知业务语义(如“同一 rowId 的多次 add 应视为 upsert”),也不维护事务间的内存一致性。试图在事务层修补(如加锁、队列化、手动查重)不仅复杂度高,还易引入死锁或延迟累积。
✅ 正确解法:采用受控组件(Controlled Component)模式,将 AgGrid 降级为纯视图层,由外部状态(如 React useState / useReducer 或 Zustand / Redux)统一持有和更新全部行数据:
// 示例:React + TypeScript 受控模式实现
const [rowData, setRowData] = useState<trade>([]);
// WebSocket 消息处理器 —— 始终操作本地状态,而非直接调用 applyTransactionAsync
useEffect(() => {
const handleMessage = (msg: WebSocketMessage) => {
setRowData(prev => {
const map = new Map<string trade>(prev.map(r => [r.id, r]));
// 增量合并:add/update 统一为 upsert 逻辑
msg.add?.forEach(row => map.set(row.id, { ...map.get(row.id) || {}, ...row }));
msg.update?.forEach(row => map.set(row.id, { ...map.get(row.id) || {}, ...row }));
msg.remove?.forEach(id => map.delete(id));
return Array.from(map.values());
});
};
ws.onmessage = handleMessage;
}, []);
// AgGrid 完全受控:仅读取 rowData,不维护内部状态
<aggridreact rowdata="{rowData}" rowselection="multiple"></aggridreact></string></trade>
⚠️ 注意事项:
- *禁用所有 `applyTransaction调用**:一旦启用受控模式,rowData属性即为单一数据源,任何直接调用applyTransactionAsync或applyTransaction` 都会导致状态不一致;
-
upsert 逻辑必须由业务层实现:利用 Map 或 Record
对 id 做快速查重与浅合并,确保同一 ID 的多次更新能叠加字段而非覆盖; - 性能保障:setRowData 触发重渲染时,AgGrid 内部已优化 diff 算法,对大规模数据(万级行)仍保持高效;如需进一步优化,可结合 immutable 数据结构或 useMemo 缓存处理结果;
- 滚动/选中状态保留:AgGrid 默认基于 getRowId(推荐显式配置)维持行节点复用,只要 rowData 中对象引用变化合理(如每次生成新数组),展开状态、选中态、分组折叠等均能自然保留。
总结而言,放弃将 AgGrid 当作状态容器,转而拥抱“状态外置 + 视图受控”的现代前端范式,不仅能彻底规避异步事务的竞态陷阱,还能提升代码可测试性、可预测性与协作清晰度——真正的高性能,始于架构的简洁与职责的分明。










