☰
ag-grid-community实战:从虚拟滚动到性能调优的大数据表格方案
2026/10/14 18:22:51 网站建设 项目流程

简介:ag-Grid Community是一款面向现代Web开发的高性能数据网格开源组件,主要服务于React、AngularJS、原生JavaScript及Web Components技术栈的前端工程师,解决大数据量表格展示、排序、过滤、分组、编辑、分页等常见交互需求。资源包共2000个文件,压缩包大小190.14MB,以js、ts源码和html、css、scss样式文件为主,附有png、gif、svg等图标素材及md说明文档,覆盖组件库完整工程目录和多样主题。目前已有1458人学习下载,适合具备一定前端基础、希望深入理解数据网格原理或快速集成表格能力的开发者。通过源码与示例,可重点学习列定义、自定义单元格渲染、事件系统、API动态配置等核心用法,并借助bootstrap、ag-theme-balham等主题文件掌握外观定制方法,整体是一份内容完整、结构清晰的开源组件学习资料。

1. 前端开源表格库选型:从 ag-grid-community 里能扒到什么

做前端的人,只要项目里出现过“要做一个能排、能筛、能分组的复杂表格”这种需求,迟早会搜到 ag-grid 这个名字。它能解决的核心问题很明确:在浏览器里高效渲染和交互上万乃至十万级行数据,同时把排序、过滤、分组、编辑、虚拟滚动这些高频表格能力做成开箱即用的配置项。社区版 ag-grid-community 免费、无 watermark,但功能边界卡在“不涉及企业级协作与部分高级图表”的位置,对绝大多数中后台项目来说已经够顶。这篇笔记适合两类人:一是正在做表格选型、想快速评估的开发现场;二是项目已经踩进表格性能泥潭、准备整套迁移的负责人。我会把资源包里的结构、集成路径、性能参数和真实翻车点按落地顺序拆开讲,不绕弯子。

2. 为什么要信社区版:功能边界、体积取舍与两种集成姿势

2.1 社区版和付费版的分界在哪

ag-grid 的发行版分为社区版与 enterprise 版,两者 API 设计高度一致,但能力边界必须在一开始确认。社区版覆盖了行/列模型、排序、过滤、分组、单元格编辑、虚拟滚动、CSV 导出、主题定制、国际化等核心功能;企业版额外提供主从表、树形数据、多行分组聚合、Excel 导出、范围选择、剪贴板操作、侧边栏工具面板等。实际评估时,我一般会先给团队画一条线:如果你的需求表里出现了“跨行合并导出到 Excel”“前端做树表且节点层级超过三层”“多选区复制粘贴”,那大概率要上付费版;只是需要在一张表里干掉十万行卡顿、做好排序筛选和行内编辑,社区版完全够。项目预算敏感时,社区版是最合理的起点,而且后续可以平滑升级,API 不需要另起炉灶。

2.2 资源包解压后的核心目录结构

拿到 ag-grid-community 资源包后,不要急着翻代码,先认目录。根目录下通常包含 packages 目录、文档入口、d.ts 类型定义目录和若干 demo 工程。值得优先看的是 packages 下的核心包(含 grid 主体与各框架封装包)、styles 目录(内置了 balham、material、alpine 等多套主题 CSS),以及 types 目录下的完整类型声明。这些类型声明是排查属性的第一手资料,很多人写 AG Grid 配置全靠记忆,翻车后才知道查接口定义。资源包里一般还会附带官方示例,示例代码按 Vanilla、Vue、React、Angular 分目录,拿到手后建议先跑通一个 Vanilla 最小 demo,再切到自己用的框架,这样能最快排除框架封装层引入的干扰。

2.3 直接上 npm 还是落地到项目目录

引入方式有两种:npm 安装依赖进项目,或者把资源包打到本地目录用相对路径引用。npm 方式是常态,执行安装命令后按框架引入核心包与样式。唯一要提醒的是作用域包名区分,社区版主包名是 ag-grid-community,企业版是 ag-grid-enterprise,两者引入路径和注册方式都不同,混用会出现“API 不存在”这类诡异报错。还有一部分离线环境或内网项目会选择本地化,把包整体拷贝进 vendor 目录。此时注意:样式文件在 node_modules 里是相对路径引用,手动拷贝时容易丢 CSS 字体文件,建议整包拷 styles 目录,不要只拖一个 ag-grid.css。下面是一个最小集成指令序列与初始化骨架。

# 以 vue3 项目为例,其它框架同理 npm install ag-grid-community --save
// main.js 里注册样式,这步最容易漏 import 'ag-grid-community/styles/ag-grid.css'; // 主题样式按需选一个,比如 balham import 'ag-grid-community/styles/ag-theme-balham.css';
// 组件内初始化网格 import { Grid } from 'ag-grid-community'; const gridOptions = { columnDefs: [ { field: 'name', headerName: '名称' }, { field: 'amount', headerName: '金额' } ], rowData: [], defaultColDef: { sortable: true, filter: true, resizable: true } }; const eGridDiv = document.querySelector('#myGrid'); new Grid(eGridDiv, gridOptions);

这段代码的逻辑链是:先拉依赖,再引入基础样式与主题样式,最后在容器节点上实例化 Grid 并传入列定义与数据。参数说明:columnDefs 定义列源,field 绑定数据字段,headerName 控制表头显示文本;defaultColDef 里统一声明列级公共行为,避免每列重复写;resizable 允许拖拽调列宽,是中后台表格刚需。实例化时容器必须是已有高度的 DOM,否则表格高度算成 0,这是新手最常见的“空白表格”原因。

3. 把表格真正接入业务:数据流、事件与列配置的完整链路

3.1 数据进表的三条路径与各自适用场景

AG Grid 支持三种数据供给方式:一次性 setRowData 全量给;通过更新 API 做局部数据变更;以及开启数据源模式,让表格按需向接口要数据。全量模式最简单,适合千行以内的静态数据;局部更新适合仪表盘这类高频刷新的场景,每次只更新变化行;数据源模式则是大数据量下的正解,表格内部会按滚动位置加载分片数据。三条路径不能随意切换,一旦用 setRowData 给过数据,再用数据源模式,表格会陷入“Loaded only”状态,不再向后端发请求。切换前必须调用重置方法清空行数据状态。

3.2 数据更新的正确姿势:immutable 与事务更新

高频数据更新是表格项目里的坑王。很多人直接拿新数组再次 setRowData,结果表格全部重绘、滚动位置丢失、筛选状态被重置。正确做法是开启 immutable 模式,即给每条数据绑定唯一 id,然后通过 updateRowData 接口传入新增、更新、删除三个集合。AG Grid 会按 id 做 diff,只重绘变化行,排序和过滤条件不会被打断。实现时用 getRowNodeId 回调告诉表格行的唯一键,这是必须的,不配 id 它只能回到全量重绘的旧路。

// 开启 immutable 模式的必要配置 const gridOptions = { rowData: [], getRowId: params => params.data.uuid }; // 持续更新数据的推荐写法 const newItems = [{ uuid: '001', name: '新条目', amount: 100 }]; const updatedItems = [{ uuid: '001', name: '改过名称', amount: 120 }]; const removedItems = ['002']; gridOptions.api.applyTransaction({ add: newItems, update: updatedItems, remove: removedItems });

这段代码的核心在 getRowId:它决定了 AG Grid 对每行数据的身份认知。有了稳定 id,applyTransaction 才能只做局部修改。参数说明:add 数组里不能包含已存在的 id,否则表格会报行节点冲突;update 的对象集合需要包含完整行数据,不是只给改动字段;remove 传入 id 数组。这里的细节是:如果服务端下发的数据里没有 uuid,前端要在一开始就加工好这个字段,别等到更新时才补,否则 id 不稳定,更新就会错乱。

3.3 列定义的八项常用配置与一个容易忽略的排序比较器

列定义是 AG Grid 配置量最大的部分。除了简单的 field/headerName,日常最多用到 width、minWidth、maxWidth、cellRenderer、cellClassRules、editable、valueFormatter、tooltipValueGetter 这八项。需要专门注意的是排序:默认按字符串字典序排,数值列会出现 10 排在 2 前面的问题。解决方式是在列定义里显式给 comparator,这是项目上线前最容易漏的检查点。下面给出一个中等复杂度的列配置片段,覆盖格式化、可编辑、工具提示、自定义排序。

const columnDefs = [ { field: 'amount', headerName: '成交金额', width: 140, cellRenderer: 'agAnimateShowChangeCellRenderer', valueFormatter: params => Number(params.value).toLocaleString('zh-CN'), editable: true, tooltipValueGetter: params => `订单号:${params.data.orderId}`, comparator: (valueA, valueB) => Number(valueA) - Number(valueB) } ];

这段配置的用途是让金额列可编辑、带数据变化动画效果、鼠标悬停显示工具提示,并在排序时走数值比较。参数说明:valueFormatter 只影响展示不影响排序,排序用的是原始值,所以 comparator 仍然要写;tooltipValueGetter 返回字符串后还需要在全局配置开启 enableBrowserTooltips 或用内置 tooltip 组件,否则字符串拿到了也不显示。agAnimateShowChangeCellRenderer 是社区版内置的变更动画渲染器,它要求检测到值变化才会触发动画,更新用的是 applyTransaction 才能生效,全量 setRowData 会把它打回原形。

3.4 事件体系:别在 api 方法里迷路

AG Grid 的事件模型覆盖面很大,从 cellClicked、cellValueChanged,到 sortChanged、filterChanged、rowDataUpdated。初学阶段最容易陷入“用 api 方法补齐一切事件逻辑”的误区,比如用 api.getDisplayedRowAtIndex 手工找行,再操作 DOM。其实更稳的做法是直接在事件回调里拿参数对象,里面带 rowNode、data、column、value。写业务代码时我习惯在 onGridReady 里做数据加载和 api 持有,其它交互一律靠事件驱动,避免 api 调用顺序不一致引发的问题。事件回调里拿到的 rowNode 可以反向 take 出来做后续修改,这比在外部通过 id 查找再 setData 安全得多。

onCellValueChanged: params => { const { data, newValue, oldValue } = params; if (newValue === oldValue) return; // 推送给后端做持久化,注意做节流 console.log('单元格改动', data.uuid, oldValue, '->', newValue); }

这段代码是编辑场景下最常见的持久化钩子。参数说明:params.data 是该行的原始数据引用,直接修改 data 再调用 rowNode.setData 会触发刷新;建议在事件回调里发送更新请求而不是自己做 DOM 操作。另外要注意,如果列设了 editable 但没设置 gridOptions 里的 editType,默认是单元格编辑完成后立即触发该事件;设置了 editType: 'fullRow' 时,整行编辑统一在编辑结束后触发,此时事件回调里拿 oldValue 与新值的差异,适合批量提交类业务。

4. 性能调优实战:从十万行卡顿到流畅滚动的关键参数

4.1 虚拟滚动原理简述与默认值陷阱

AG Grid 之所以能处理大数据量,核心是虚拟滚动:它只渲染可视区域的行节点,滚动时复用 DOM 行元素,行数据与视图分离。但默认配置并不一定适合你的数据集,关键参数 rowBuffer 决定可视区上下额外渲染的行数,默认是 10。在快速滚动时,默认值会导致白屏闪烁;调大到 20 或 30 能缓解,但代价是渲染数量上升。另一个参数是 suppressRowVirtualisation,有人误以为关掉虚拟滚动会让表格更流畅,实际结果反而是所有行一次性渲染,十万行直接让浏览器崩溃。这个参数默认 false,保持默认就是对的。

const gridOptions = { rowBuffer: 20, suppressRowVirtualisation: false, // 默认不开启,数据量大且列数量多时有效 suppressColumnVirtualisation: false };

这里的关键说明:rowBuffer 调大是快速滚动场景的首选手段;suppressColumnVirtualisation 对应列虚拟化,列数超过 30 列时,开启列虚拟化会显著降低横向滚动卡顿。参数设置的取舍是——数据行数多,优先调 rowBuffer 并配合服务端分页;列数多,优先确认列虚拟化开启。日常排查时可以用浏览器 Performance 面板录制滚动过程,看 Scripting 时间是否占比过高,如果高就把重心放到数据供给模式上,别只调 buffer。

4.2 服务端数据源模式:把十万行压力分摊给接口

当前端一次性拿十万行 JSON 并把它们交给 gridOptions 时,即使虚拟滚动能撑住渲染,首次数据解析和内存占用依然可观。更稳妥的方案是服务端数据源模式:表格滚动到未加载区域时,自动携带 startRow/endRow 请求后端,后端返回对应切片数据并告知总行数。这种模式下,前端内存里永远只保留已加载的行,内存占用断崖式下降。配置上不需要传 rowData,而是提供 datasource 对象,核心方法 getRows 里调用成功回调。

const gridOptions = { rowModelType: 'serverSide', serverSideDatasource: { getRows(params) { const { startRow, endRow, successCallback } = params; fetch('/api/orders', { method: 'POST', body: JSON.stringify({ startRow, endRow, sortArr: params.sortModel }) }) .then(res => res.json()) .then(data => { successCallback(data.rows, data.totalCount); }); } } };

参数说明:rowModelType 改为 serverSide 后,表格不再监听 rowData;getRows 里 param 对象携带 startRow、endRow、sortModel、filterModel。successCallback 的第一个参数是行数据数组,第二个是总行数,用于撑起滚动条高度。这个模式下有三个容易出错的地方:一是后端必须支持基于 startRow/endRow 的切片查询,否则数据错位;二是排序必须把 sortModel 传给后端,前端排序逻辑在服务端模式下默认禁用;三是后端返回的总行数与实际返回条数不一致时,滚动条长度会失真,跳动明显。

4.3 加载体验的补充算法:缓存块与节流

服务端模式还附带一个块缓存机制,即表格按块大小从后端取数。cacheBlockSize 默认是 20 行,我认为在中后台场景设置 50 到 100 行更合理,能减少请求次数,但首屏填充会稍慢。配合 maxConcurrentDatasourceRequests(默认 2)控制并发请求数,后端压力敏感型项目可以降为 1,做一个请求队列效果。网络抖动场景还可以用 datasource 的 failCallback 通知表格加载失败,并在网格外层叠加重试按钮。这些参数是资源包里文档着墨多但代码示例少的部分,落地时建议先用小数据集模拟 1 万行,验证缓存块的命中行为和重试表现,再切真实大表。

5. 集成避坑记录:样式丢失、id 冲突与数据源切换的四个真实翻车现场

5.1 翻车一:表格渲染出来了,但所有表头贴在一起,样式像没加载

现象:列能显示,数据也正常填充,但界面没有网格线、表头没有背景色、滚动条异常粗糙。原因:基础样式与主题样式只引用了其中一个,或者引了 ag-grid.css 但没引主题文件。解决:检查 main.js 里两个样式文件是否同时引入;如果有自定义主题,确认自定义 CSS 覆盖的顺序在后,并且没有把 ag-grid.css 里的核心布局类误删。这类问题最常见于从资源包拷贝源码场景,拷少了 styl 目录下的字体与图标文件,导致主题变量解析失败。排查方式直接看 Network 面板内字体文件是否 404。

5.2 翻车二:immutable 模式下 applyTransaction 更新不生效,行数据一直停留在旧值

现象:调用了 applyTransaction,但页面行内容不变,甚至控制台出现 “Could not find row with id” 警告。原因:getRowId 回调里返回的 id 与数据中实际字段不一致,或 id 不唯一,导致 AG Grid 无法把事务映射到具体行。解决:在 getRowId 中用 key 拼接字段,确认服务端数据里该字段全局唯一;如果数据可能重名,就加前缀做复合 id,例如返回${data.orderType}-${data.orderNo}。代码走查时要连分页数据一起看,分页跨页时同样的 id 必须代表同一业务实体,否则更新会串行。

// getRowId 必须是稳定且全局唯一的复合 id getRowId: params => { return params.data.orderType + '-' + params.data.orderNo; }

这段代码说明的是复合 id 的写法。常见做法是在数据进入表格前,前端把复合 id 加工成独立字段,比如 data.rowKey =${orderType}-${orderNo},后续所有事务都引用 rowKey,比在 getRowId 里临时拼接更不容易出错。还有一个隐藏细节:id 里不能包含中文以外的特殊符号,某些字符集环境下会触发编码问题导致匹配失败,建议统一用连字符。

5.3 翻车三:切换 rowModelType 后表格白发不出数据请求

现象:从 setRowData 模式改为 serverSide 模式之后,表格空白且没有网络请求。原因:gridOptions 一旦实例化,rowModelType 不能热切换,需要重新创建 grid 实例或者刷新整个容器。解决:切换数据源模式时,先销毁旧的 Grid 实例,再在同一个 DOM 容器上重新 new Grid,并把缓存清掉。还有一种低频场景是只改了 rowModelType 但忘记移除 gridOptions.rowData,数据与数据源模式互斥,表格会进入未知状态,直接表现为不渲染。这两种情况在大型系统迁移时出现率最高,迁移前建议先写一个切换机器的回归用例。

5.4 翻车四:刷新数据后滚动条回顶,用户操作被打断

现象:定时轮询刷新表格数据时,滚动条每次都跳到顶部,正在查看的某一行丢失。原因:全量 setRowData 触发表格整体重置,视图定位失效。解决:使用 immutable 数据模式并保证 getRowId 存在,这样更新时只做局部刷新,滚动位置得以保留。注意 applyTransaction 只负责行变更,不改变当前滚动位置;如果你在调用事务后又手动执行 api.ensureIndexVisible 或者 ensureNodeVisible,反而会打乱位置,除非业务确实需要复位,否则不要主动调用定位接口。

5.5 一个常被忽略的注意点:key 配置与列配置的“失效感”

现象:表格列按条件动态切换 columnDefs,切换后部分列的排序状态丢失,或者自定义渲染器没有刷新。原因:AG Grid 对列定义按列对象引用做 diff,没有稳定 key 时它会把新旧列当作不同实体,导致状态丢失。解决:给每个列定义增加 colId 字段,固定列的唯一标识。colId 不影响展示字段绑定,但会影响所有列级状态的持久化,包括列宽、排序、过滤和单元格编辑状态。上线排查询题时,先确认每列都有 colId,一抓一个准。

6. 进阶玩法:把整张表的状态序列化,并做一个可复用的表格封装壳

这套东西玩到后期,最有价值的不是单张表写得多漂亮,而是把表格状态完全掌握在开发手里。AG Grid 提供 getColumnState 与 restoreColumnState,能把所有列的宽度、排序、过滤、分组状态导出成纯 JSON;配合表格实例的 getFilterModel 能拿到过滤条件模型,page 信息则从 paginationProxy 里取。把这几个快照拼起来存到本地存储或 URL 参数里,用户下次打开页面,表格能原样恢复到上次操作位置。

// 保存表格状态到 localStorage function saveGridState() { const colState = gridOptions.api.getColumnState(); const filterState = gridOptions.api.getFilterModel(); localStorage.setItem('grid-prefs', JSON.stringify({ colState, filterState, pageSize: gridOptions.api.paginationGetPageSize() })); } // 恢复表格状态 function restoreGridState() { const raw = localStorage.getItem('grid-prefs'); if (!raw) return; const saved = JSON.parse(raw); gridOptions.api.restoreColumnState(saved.colState); gridOptions.api.setFilterModel(saved.filterState); }

这段代码的核心是状态快照与恢复。restoreColumnState 必须在列定义已经初始化完成后调用,否则会报列找不到的错误;setFilterModel 传入 null 可以清空所有过滤条件。参数说明:getColumnState 返回的数组里包含 colId、width、sort、sortIndex、filter 等关键字段,restore 时依赖 colId 定位列,这就是前面强调 colId 必须稳定的原因。paginate 场景下,分页大小也要一起存储,否则恢复完列状态但每页条数还是默认值,体验差一截。

我一般会把这些状态操作封装进一个表格组件壳里,对外只暴露 dataSource 与 columnDefs 两个入口,内部统一处理加载态、错误态与状态持久化。调用方不直接触碰 grid api,所有交互事件由壳组件捕获后再按业务分发,这样项目里十几个表格页面可以保持一致的交互行为,出现问题时只需要改壳组件一处。从那以后我每次接到表格需求,都强制先确认三件事:数据量级预估、是否需要状态记忆、是否有跨端复用要求。这三件事定了,再决定用社区版的哪套能力组合,最后动手写壳。这一套经验来自踩过的不少坑,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询