开启服务端行分组的AG Grid切换过滤器时触发多次API调用问题
AG Grid服务端行分组切换过滤器触发多次API调用问题
问题描述
启用服务端行分组(server-side row grouping)的AG Grid组件,切换过滤器时会触发多次API调用,极端情况甚至持续调用。未开启行分组的同配置组件无此问题,每次切换仅触发一次调用。
调试确认:
- 调用
refreshServerSide()的useEffect仅触发一次 getServerSideDatasource的getRows方法中,params.request会返回两次,导致两次API请求
已尝试操作
- 确认
refreshServerSide()仅执行一次 - 验证切换过滤器时
params.request返回两次 - 对比未启用行分组的同逻辑组件,确认后者运行正常
预期与实际行为
- 预期:切换过滤器仅触发一次API调用
- 实际:切换单个过滤器触发两次或多次调用,部分场景持续调用
组件相关代码
const memoizedFilters = useMemo(() => filters, [JSON.stringify(filters)]); useEffect(() => { if(filtersRef.current !== filters) { filtersRef.current = filters; if (gridApiRef.current && !gridApiRef.current.isDestroyed()) { console.log("Refreshing server-side data with new filters"); gridApiRef.current.refreshServerSide(); } } }, [memoizedFilters]); const autoGroupColumnDef = useMemo<ColDef>(() => ({ headerName: "Group", cellRendererParams: { suppressCount: false, suppressDoubleClickExpand: false, innerRenderer: () => "", }, }), []); const getServerSideDatasource = (): IServerSideDatasource => { return { getRows: async (params: IServerSideGetRowsParams) => { console.log("Requesting rows", params.request); const newStartRow = params.request.startRow ?? 0; const newEndRow = params.request.endRow ?? 10; if (params.request.groupKeys.length && params.request.groupKeys[0] !== "") { const campaign_id = params.request.groupKeys[0]; const campaignFilters = filtersRef.current; try { const res = await fetchCampaignOverview({ startRow: newStartRow, endRow: newEndRow, campaign_id, ...campaignFilters }); if (res.rows.length) { params.success({ rowData: res.rows, rowCount: res.totalRows }); } else { params.success({ rowData: [], rowCount: 0 }); } } catch (error) { console.error("Error fetching rows:", error); params.fail(); } } else if (params.request.groupKeys[0] === "") { const unGroupedData = fetchedDataRef.current.filter((item) => item.campaign_id === ""); console.log("Fetching ungrouped data", unGroupedData); params.success({ rowData: unGroupedData, rowCount: unGroupedData.length }); } else { try { const campaignFilters = filtersRef.current; const res = await fetchCampaignOverview({ startRow: newStartRow, endRow: newEndRow, ...campaignFilters }); if (res.rows.length) { const unbalancedData = res.rows.map((item) => ({ ...item, campaign_id: item.has_child ? item.campaign_id : "", })); fetchedDataRef.current = unbalancedData; const groupedData = unbalancedData.filter((item) => item.campaign_id !== ""); const unGroupedData = unbalancedData.filter((item) => item.campaign_id === ""); if (unGroupedData.length > 0) groupedData.push(unGroupedData[0]); params.success({ rowData: groupedData, rowCount: res.totalRows }); } else { params.success({ rowData: [], rowCount: 0 }); } } catch (error) { params.fail(); console.error("Error fetching rows:", error); } } }, }; }; const onGridReady = (params: GridReadyEvent) => { console.log("Grid is ready"); if (!gridApiRef.current || gridApiRef.current.isDestroyed()) { gridApiRef.current = params.api const datasource = getServerSideDatasource(); params.api.setGridOption("serverSideDatasource", datasource); } }; const paginationPageSizeSelector = useMemo(() => [10, 20, 50, 100], []); return ( <> <CampaignFilters onFilterChange={(filters: CampaignFilter) => { setFilters(filters); }} /> <TableContainer> <AgGridReact autoGroupColumnDef={autoGroupColumnDef} cacheBlockSize={paginationPageSize} columnDefs={colDefs} defaultColDef={defaultColDef} domLayout="autoHeight" blockLoadDebounceMillis={1000} // getServerSideGroupKey={(params: { data: CampaignOverviewRowData }) => params.data.campaign_id || ""} groupAllowUnbalanced={true} onGridReady={onGridReady} onPaginationChanged={() => { if (gridApiRef.current) { const pageSize = gridApiRef.current.paginationGetPageSize(); setPaginationPageSize(pageSize); } }} paginateChildRows={true} pagination={true} paginationPageSize={10} paginationPageSizeSelector={paginationPageSizeSelector} rowModelType="serverSide" /> </TableContainer> </> ); };
问题根源与解决方案
根源分析
服务端行分组模式下,开启groupAllowUnbalanced: true时,AG Grid会同时发起两个请求:
- 根分组数据请求(
groupKeys为空数组) - 未分组顶级数据请求(
groupKeys[0] === "")
加上你在根请求中修改fetchedDataRef.current触发的变更检测,会导致额外的重复调用。
解决方案
1. 合并根请求与未分组数据逻辑
去掉groupKeys[0] === ""的分支,将未分组数据逻辑整合到根请求中,避免重复API调用:
const getServerSideDatasource = (): IServerSideDatasource => { return { getRows: async (params: IServerSideGetRowsParams) => { console.log("Requesting rows", params.request); const newStartRow = params.request.startRow ?? 0; const newEndRow = params.request.endRow ?? 10; const campaignFilters = filtersRef.current; // 处理分组子节点请求 if (params.request.groupKeys.length > 0 && params.request.groupKeys[0] !== "") { const campaign_id = params.request.groupKeys[0]; try { const res = await fetchCampaignOverview({ startRow: newStartRow, endRow: newEndRow, campaign_id, ...campaignFilters }); params.success({ rowData: res.rows || [], rowCount: res.totalRows || 0 }); } catch (error) { console.error("Error fetching group rows:", error); params.fail(); } return; } // 统一处理根分组数据 try { const res = await fetchCampaignOverview({ startRow: newStartRow, endRow: newEndRow, ...campaignFilters }); if (!res.rows.length) { params.success({ rowData: [], rowCount: 0 }); return; } // 处理分组标记,无需存储到ref const processedData = res.rows.map(item => ({ ...item, campaign_id: item.has_child ? item.campaign_id : "" })); // 根分组仅返回需要分组的节点,未分组数据由AG Grid自动处理 const rootGroupData = processedData.filter(item => item.campaign_id !== ""); params.success({ rowData: rootGroupData, rowCount: res.totalRows }); } catch (error) { console.error("Error fetching root rows:", error); params.fail(); } }, }; };
2. 优化过滤器更新逻辑
修正memoizedFilters的依赖,并添加purgeCache确保完全刷新:
// 直接拷贝filters对象作为memo值,避免JSON.stringify的不必要计算 const memoizedFilters = useMemo(() => ({...filters}), [filters]); useEffect(() => { if (gridApiRef.current && !gridApiRef.current.isDestroyed()) { console.log("Refreshing server-side data with new filters"); // 添加purgeCache清空缓存,避免旧数据触发额外请求 gridApiRef.current.refreshServerSide({ purgeCache: true }); } }, [memoizedFilters]);
3. 调整分页配置(可选)
如果不需要子分组分页,关闭paginateChildRows减少额外请求:
<AgGridReact // ...其他配置 paginateChildRows={false} />
内容的提问来源于stack exchange,提问作者dev here
相关产品推荐
相关产品推荐

