引言
ag-Grid 提供多种行模型(Row Model)的核心原因在于应对不同规模数据和场景的技术需求。虽然社区版(ag-grid-community
)和企业版(ag-grid-enterprise
)在功能上存在差异,但模型设计的本质区别源于技术实现需求,不仅仅是为了区分不同功能进行收费:
- 数据量级差异:万级与百万级数据的处理策略不同
- 计算位置选择:前端计算与服务端计算的性能权衡
- 实时性要求:静态数据与流式数据的更新机制差异
本文将从技术实现角度解析各模型的特性和适用场景。
本文以ag-grid v33版本为准
一、核心概念对比:Client-Side vs Server-Side
1. 数据处理模式差异
四大模型核心差异
模型类型 | 数据加载方式 | 内存管理 | 适用场景 | 企业版依赖 |
---|---|---|---|---|
Client-Side | 全量加载 | 浏览器内存 | <10万行静态数据 | 否 |
Infinite | 分页预加载 | 滚动缓存池 | 中等规模可滚动数据 | 否 |
Server-Side | 服务端计算+按需加载 | 服务端内存 | 百万级OLAP场景 | 是 |
Viewport | 视窗动态加载 | 可视区域维护 | 实时流数据(如股票行情) | 是 |
2. 架构原理示意图
graph TD
subgraph ag-Grid 架构图
A[Grid Core] --> B[Grid API]
A --> C[Column Definitions]
A --> D[Row Models]
A --> E[Events]
A --> F[Rendering Engine]
D --> D1[Client-Side Row Model]
D --> D2[Server-Side Row Model]
D --> D3[Infinite Row Model]
D --> D4[Viewport Row Model]
B --> B1[Sorting]
B --> B2[Filtering]
B --> B3[Pagination]
B --> B4[Selection]
E --> E1[Row Click]
E --> E2[Cell Edit]
E --> E3[Model Updated]
F --> F1[DOM Renderer]
F --> F2[Virtual Scroll]
F --> F3[Cell Renderers]
F --> F4[Cell Editors]
subgraph Data Flow
G[Data Source] -->|Client-Side| H[rowData]
G -->|Infinite/Server-Side/Viewport| I[dataSource/serverDataSource/viewportDataSource]
H --> D1
I --> D2
I --> D3
I --> D4
J[Server] -->|HTTP| I
end
subgraph State Management
K[Filter Model]
L[Sort Model]
M[Pagination State]
N[Row Selection]
B2 --> K
B1 --> L
B3 --> M
B4 --> N
end
D1 -->|On| F2
D2 -->|Off| F2
D3 -->|Partial| F2
end
style A fill:#4CAF50,stroke:#333
style D fill:#2196F3,stroke:#333
style B fill:#FF9800,stroke:#333
style E fill:#9C27B0,stroke:#333
style F fill:#E91E63,stroke:#333
style G fill:#00BCD4,stroke:#333
style K fill:#8BC34A,stroke:#333
style J fill:#607D8B,stroke:#333
二、Client-Side Model
ag-grid的设计概念是如果使用了clientSide
,那么你应该一次性将所有数据传给ag-gird,因为后续的排序、过滤等功能都是在前端完成。如果数据实在太多,可以在前端做假分页。
技术特性
- 全量数据驻留内存:浏览器需加载完整数据集
- 前端计算开销:排序/过滤/分组均由JavaScript处理
- 内存限制:Chrome单个页面内存限制约1.4GB(10万行数据约占用200MB)
基础配置模板
// 没有特别指明rowModelType,则默认值就是clientSide
const gridOptions = {
rowData: [...],
columnDefs: [],
}
分页
const gridOptions = {
rowData: [...],
columnDefs: [],
pagination: true, // 开启分页
paginationAutoPageSize: true, // 根据ag-grid表格高度计算每一页可显示的行数pageSize
paginationPageSizeSelector: [100, 200, 400], // 设置可选择的分页设置,默认是[20, 50, 100]
paginationPageSize: 200, // 设置当前选择的分页设置,默认是20
paginationNumberFormatter(params) { // 设置分页面板格式化
return "[" + params.value.toLocaleString() + "]";
}
}
自定义分页面板
const [current, setCurrent] = useState(0);
const gridRef = useRef<AgGridReact<IOlympicData>>(null);
const gridOptions = {
...
pagination: true,
suppressPaginationPanel: true, // 禁用分页面板
}
const onPaginationChanged = useCallback(() => {
const current = gridRef.current!.api.paginationGetCurrentPage() + 1;
setCurrent(current);
}, []);
const onChange = (pageNumber) => {
gridRef.current!.api.paginationGoToPage(pageNumber);
}
return (
<div>
<AgGridReact<IData>
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
owSelection={rowSelection}
paginationPageSize={500}
paginationPageSizeSelector={paginationPageSizeSelector}
pagination={true}
suppressPaginationPanel={true} // 禁用分页面板
suppressScrollOnNewData={true} // 在页面更改时不要滚动到顶部,保持在之前滚动的位置
onGridReady={onGridReady}
onPaginationChanged={onPaginationChanged}
/>
<Pagination current={current} total={total} onChange={onChange} />
</div>
)
二、Infinite Model
infinite
和clientSide
一样,也是社区版提供的功能。
当我们看到infinite
这个名字的时候,往往会产生误解,误以为只能进行上拉加载更多的操作。infinite
英文翻译为无限。官方解释是:无限滚动允许网格根据网格的滚动位置从服务器懒加载行。在最简单形式中,用户滚动越多,加载的行就越多 但是它其实不仅仅如此,同样可以通过pagination配置,做到常规的分页功能。
那么如果我们使用clientSide
模式,且也配置了pagination
。和使用了infinite
,且配置了pagination
的方式有什么区别呢?
首先,clientSide
并不在乎数据的来源在哪,只要rowData
提供了数据就行,后续所有的排序、过滤等操作都只在前端完成。
而infinite
模式不同,你可以把它看成是serverSide
的简化版,它也是需要后端接口支持的,而且默认情况下,排序、过滤都是会重新查询接口的。
有一种情况需要特殊讨论一下,如果隐藏了一列,是否需要重新请求数据?假设这一列没有进行过过滤或排序的操作,则不需要重新请求数据,如果进行了过滤或排序的操作,就需要重新请求数据了。
核心限制
- 仅支持简单分页加载
- 服务端过滤/排序需手动实现
- 不支持分组聚合
基础配置模板
const gridOptions = {
rowModelType: 'infinite',
columnDefs: [],
dataSource: {
rowCount: undefined,
getRows(params) {
fetch('api/list', {
method: 'post',
body: JSON.stringify(params.request),
headers: { 'Content-Type': 'application/json; charset=utf-8' }
})
.then(httpResponse => httpResponse.json())
.then(response => {
params.successCallback(response.rows, response.lastRow);
})
.catch(error => {
console.error(error);
params.failCallback();
})
}
}
}
可以看到,clientSide
是通过rowData属性为表格提供数组数据的,而infinite
,包括后面的serverSide
、viewport
都是类似于dataSource的方式提供的获取数据的函数。
分页
const gridOptions = {
...
pagination: true, // 开启分页
paginationAutoPageSize: true, // 根据ag-grid表格高度计算每一页可显示的行数pageSize
}
三、Server-Side Model
serverSide
和infinite
类似,但是功能比infinite
更多。还支持分组和树形数据的动态加载。从使用效果看,ag-grid提供serverSide
,是为了封装一系列的接口请求,让用户使用起来更简单。它定义了数据的查询、过滤、排序等接口参数的定义。使用的时候,只需要按照它规定的参数传递给后端,就啥也不用管了。
基础配置模板
const gridOptions = {
rowModelType: 'serverSide',
columnDefs: [
{ field: 'id', filter: 'agNumberColumnFilter' },
{ field: 'timestamp', filter: 'agDateColumnFilter' },
{ field: 'value', aggFunc: 'avg' }
],
serverDataSource: {
rowCount: undefined,
getRows(params) {
fetch('api/list', {
method: 'post',
body: JSON.stringify(params.request),
headers: { 'Content-Type': 'application/json; charset=utf-8' }
})
.then(httpResponse => httpResponse.json())
.then(response => {
params.successCallback(response.rows, response.lastRow);
})
.catch(error => {
console.error(error);
params.failCallback();
})
}
}
};
四、Viewport Model
如果你不知道你要不要使用Viewport Model,那么你就不应该使用Viewport Model。在这个模式下,ag-grid只渲染当前可见的行。它适用于需要展示大量的实时数据。
基础配置模板
function createViewportDataSource(server) {
class ViewportDataSource {
...实现一个可以实时刷新数据的class
}
}
const gridOptions = {
rowModelType: 'serverSide',
columnDefs: [
{ field: 'id', filter: 'agNumberColumnFilter' },
{ field: 'timestamp', filter: 'agDateColumnFilter' },
{ field: 'value', aggFunc: 'avg' }
],
viewportDatasource: createViewportDataSource(server),
};
五、决策流程图与选型建议
1. 模型选型决策树
graph TD
A[总数据量] --> B{小于10万行?}
B -->|是| C[Client-Side]
B -->|否| D{需要以下功能?<br/>- 服务端计算<br/>- 动态分组<br/>- 实时聚合}
D -->|是| E[Server-Side]
D -->|否| F{滚动加载?}
F -->|是| G[Infinite]
F -->|否| H{高频更新?}
H -->|是| I[Viewport]
实际场景实际分析
在实际场景中,我们公司还是使用了clientSide
+ 自定义实现分页。一个是因为serverSide
需要后端接口支持,不能为了前端组件的需求而要求后端所有的接口改一遍。
另外也是因为我们的场景是后台管理系统。页面的结构一般是上部分是查询表单,下部分是表格。查询表格已经包含了过滤的需求了,而serverSide
模式下,不支持表格的Quick Filter,且会与上部分的查询表格需求重复了。