AG Grid 源码分析:单元格渲染与复杂数据处理
2026/3/28
AG GridReact单元格渲染性能优化前端
AG Grid 单元格渲染性能优化机制深度分析
目录
- 架构概览
- React 组件 vs JS 渲染器选择策略
- 单元格更新策略
- DOM 操作优化
- React 与 AG Grid 桥接层
- 骨架单元格机制
- 延迟渲染策略
- 值缓存机制
- 单元格回收与复用
- 其他性能技巧
架构概览
AG Grid 的单元格渲染系统采用分层架构设计:
┌─────────────────────────────────────────────────────────────┐
│ Framework Layer │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ React CellComp (cellComp.tsx) │ │
│ │ - Suspense 包装 │ │
│ │ - useJsCellRenderer Hook │ │
│ │ - SkeletonCellRenderer 降级 │ │
│ └─────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Component Layer │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CellComp (cellComp.ts) │ │
│ │ - DOM 元素管理 │ │
│ │ - 渲染器/编辑器生命周期 │ │
│ │ - CSS 类管理 │ │
│ └─────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Controller Layer │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CellCtrl (cellCtrl.ts) │ │
│ │ - 业务逻辑 │ │
│ │ - 值获取与格式化 │ │
│ │ - 事件处理 │ │
│ │ - 状态管理 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
核心组件职责
| 组件 | 职责 | 文件 |
|---|---|---|
CellCtrl | 单元格控制器,管理业务逻辑和状态 | cellCtrl.ts |
CellComp | DOM 层组件,管理原生 DOM 操作 | cellComp.ts |
CellComp (React) | React 组件,协调 React 渲染 | cellComp.tsx |
showJsRenderer | JS 渲染器桥接 Hook | showJsRenderer.tsx |
React 组件 vs JS 渲染器选择策略
判断逻辑
AG Grid 通过 componentFromFramework 标志区分渲染器类型:
// cellCtrl.ts - 判断是否有 Cell Renderer
public isCellRenderer(): boolean {
const colDef = this.column.getColDef();
return colDef.cellRenderer != null || colDef.cellRendererSelector != null;
}
React 端的选择策略
// cellComp.tsx - 核心渲染逻辑
const valueOrCellComp = () => {
const { compDetails, value } = renderDetails!;
if (!compDetails) {
// 无 Cell Renderer,直接显示值
return value?.toString?.() ?? value;
}
if (compDetails.componentFromFramework) {
// React 组件:使用 Suspense 包装
const CellRendererClass = compDetails.componentClass;
return (
<Suspense fallback={<SkeletonCellRenderer cellCtrl={cellCtrl} parent={eGui} />}>
{reactCellRendererStateless ? (
<CellRendererClass {...compDetails.params} key={renderKey} />
) : (
<CellRendererClass {...compDetails.params} key={renderKey} ref={cellRendererRef} />
)}
</Suspense>
);
}
// JS 渲染器由 useJsCellRenderer Hook 处理
};
JS 渲染器专用 Hook
// showJsRenderer.tsx - 处理非框架渲染器
const useJsCellRenderer = (
showDetails: RenderDetails | undefined,
showTools: boolean,
eCellValue: HTMLElement | undefined | null,
cellValueVersion: number,
jsCellRendererRef: MutableRefObject<ICellRendererComp | undefined>,
eGui: MutableRefObject<any>,
suppressInlineEditRenderer = false
) => {
// 创建或刷新 JS cell renderer
useEffect(() => {
const showValue = showDetails != null && !suppressInlineEditRenderer;
const jsCompDetails = showDetails?.compDetails && !showDetails.compDetails.componentFromFramework;
const waitingForToolsSetup = showTools && eCellValue == null;
const showComp = showValue && jsCompDetails && !waitingForToolsSetup;
if (!showComp) {
destroyCellRenderer();
return;
}
// 尝试刷新现有组件
if (jsCellRendererRef.current) {
const comp = jsCellRendererRef.current;
const attemptRefresh = comp.refresh != null && showDetails!.force == false;
const refreshResult = attemptRefresh ? comp.refresh(compDetails!.params) : false;
const refreshWorked = refreshResult === true || refreshResult === undefined;
if (refreshWorked) {
return; // 刷新成功,无需重建
}
destroyCellRenderer();
}
// 创建新组件
const promise = compDetails!.newAgStackInstance();
promise.then((comp) => {
if (!comp) return;
const parent = showTools ? eCellValue! : eGui.current!;
parent.appendChild(comp.getGui());
jsCellRendererRef.current = comp;
});
}, [showDetails, showTools, cellValueVersion, suppressInlineEditRenderer]);
};
选择策略总结
| 条件 | 渲染方式 | 特点 |
|---|---|---|
!compDetails | 直接显示 value.toString() | 最轻量,无额外组件 |
componentFromFramework === true | React 组件 | 支持 Suspense、React 生命周期 |
componentFromFramework === false | JS 渲染器 | 由 Hook 手动管理 DOM |
单元格更新策略
三种更新模式
1. 跳过更新(Skip Update)
// cellCtrl.ts - refreshCell 方法
public refreshCell(params?: RefreshCellsParams & { newData?: boolean }): void {
// suppressRefreshCell 标志可阻止刷新
if (suppressRefreshCell) {
return;
}
// 比较值是否变化
const valuesDifferent = this.updateAndFormatValue(isCellCompReady);
const dataNeedsUpdating = forceRefresh || valuesDifferent;
// 只有在数据需要更新时才执行
if (!dataNeedsUpdating) {
return; // 跳过更新
}
// ... 执行更新
}
2. 值比较逻辑
// cellCtrl.ts - 值更新与格式化
public updateAndFormatValue(compareValues: boolean): boolean {
const oldValue = this.value;
const oldValueFormatted = this.valueFormatted;
const { value, valueFormatted } = this.beans.valueSvc.getValueForDisplay({
column: this.column,
node: this.rowNode,
includeValueFormatted: true,
from: 'edit',
});
this.value = value;
this.valueFormatted = valueFormatted;
if (compareValues) {
return !this.valuesAreEqual(oldValue, this.value) || this.valueFormatted != oldValueFormatted;
}
return true;
}
private valuesAreEqual(val1: any, val2: any): boolean {
const colDef = this.column.getColDef();
// 支持自定义 equals 方法
return colDef.equals ? colDef.equals(val1, val2) : val1 === val2;
}
3. 强制更新
// cellCtrl.ts - 强制刷新条件
const noValueProvided = field == null && valueGetter == null && showRowGroup == null;
const newData = params?.newData ?? false;
const forceRefresh = noValueProvided || (params && (params.force || newData));
强制更新的触发条件:
- 调用 API
refreshCells({ force: true }) - 新数据加载(
newData: true) - 列定义中无
field、valueGetter、showRowGroup(无法判断值变化)
Cell Renderer 刷新机制
// cellComp.ts - 尝试刷新渲染器
private refreshCellRenderer(compClassAndParams: UserCompDetails): boolean {
// 没有 refresh 方法则无法刷新
if (this.cellRenderer?.refresh == null) {
return false;
}
// 组件类变化则不刷新,强制重建
if (this.cellRendererClass !== compClassAndParams.componentClass) {
return false;
}
// 调用 refresh 方法
const result = this.cellRenderer.refresh(compClassAndParams.params);
// 兼容性处理:undefined 视为刷新成功
return result === true || result === undefined;
}
React 端的刷新策略
// cellComp.tsx - useLayoutEffect 处理刷新
useLayoutEffect(() => {
const oldDetails = lastRenderDetails.current;
const newDetails = renderDetails;
lastRenderDetails.current = renderDetails;
// 如果没有更新 renderDetails,跳过
if (oldDetails == null || oldDetails.compDetails == null ||
newDetails == null || newDetails.compDetails == null) {
return;
}
const oldCompDetails = oldDetails.compDetails;
const newCompDetails = newDetails.compDetails;
// 组件类不同,不处理(会重建)
if (oldCompDetails.componentClass != newCompDetails.componentClass) {
return;
}
// 没有 refresh 方法,不处理
if (cellRendererRef.current?.refresh == null) {
return;
}
// 调用 refresh
const result = cellRendererRef.current.refresh(newCompDetails.params);
if (result != true) {
// refresh 返回 false,强制通过 key 变化触发重建
setRenderKey((prev) => prev + 1);
}
}, [renderDetails]);
DOM 操作优化
1. 最小化 DOM 操作
// cellComp.ts - 检查是否需要更新
const nothingHasChanged = !!this.classesMap[className] == on;
if (nothingHasChanged) {
return this; // 返回原对象,避免 React 重渲染
}
2. 智能的 Wrapper 管理
// cellComp.ts - 动态管理 DOM 结构
private refreshWrapper(editing: boolean): boolean {
const providingControls = this.includeRowDrag || this.includeDndSource || this.includeSelection;
const usingWrapper = providingControls || this.forceWrapper;
// 按需创建 wrapper
const putWrapperIn = usingWrapper && this.eCellWrapper == null;
if (putWrapperIn) {
this.eCellWrapper = _createElement({ tag: 'div', cls: 'ag-cell-wrapper', role: 'presentation' });
this.eCell.appendChild(this.eCellWrapper);
}
// 不需要时移除 wrapper
const takeWrapperOut = !usingWrapper && this.eCellWrapper != null;
if (takeWrapperOut) {
_removeFromParent(this.eCellWrapper);
this.eCellWrapper = undefined;
}
return templateChanged;
}
3. innerHTML vs DOM API
AG Grid 在不同场景使用不同策略:
简单值显示:
// cellComp.ts - 使用 textContent
private insertValueWithoutCellRenderer(valueToDisplay: any): void {
const eParent = this.getParentOfValue();
_clearElement(eParent);
const escapedValue = _toString(valueToDisplay);
if (escapedValue != null) {
eParent.textContent = escapedValue; // 安全且高效
}
}
复杂组件:
// cellComp.ts - 使用 DOM API
private afterCellRendererCreated(
cellRendererVersion: number,
cellRendererClass: any,
cellRenderer: ICellRendererComp
): void {
// ...
if (cellGui != null) {
const eParent = this.getParentOfValue();
_clearElement(eParent);
eParent.appendChild(cellGui); // DOM API 添加
}
}
4. 批量更新与版本控制
// cellComp.ts - 版本控制防止过时操作
private rendererVersion = 0;
private editorVersion = 0;
private createCellRendererInstance(compDetails: UserCompDetails): void {
const displayComponentVersionCopy = this.rendererVersion;
const createCellRendererFunc = (details: UserCompDetails) => (_?: boolean) => {
// 检查版本是否过期
const staleTask = this.rendererVersion !== displayComponentVersionCopy || !this.isAlive();
if (staleTask) {
return; // 丢弃过期任务
}
// ...
};
}
React 与 AG Grid 桥接层
ICellComp 接口
// cellCtrl.ts - 组件接口定义
export interface ICellComp {
toggleCss(cssClassName: string, on: boolean): void;
setUserStyles(styles: CellStyle): void;
getFocusableElement(): HTMLElement;
setIncludeSelection(include: boolean): void;
setIncludeRowDrag(include: boolean): void;
setIncludeDndSource(include: boolean): void;
setRowResizerElement(element: HTMLElement | null): void;
getCellEditor(): ICellEditor | null;
getCellRenderer(): ICellRenderer | null;
getParentOfValue(): HTMLElement | null;
setRenderDetails(
compDetails: UserCompDetails | undefined,
valueToDisplay: any,
forceNewCellRendererInstance: boolean
): void;
setEditDetails(
compDetails?: UserCompDetails,
popup?: boolean,
position?: 'over' | 'under',
reactiveCustomComponents?: boolean
): void;
refreshEditStyles: (editing: boolean, isPopup: boolean) => void;
}
React 端的代理实现
// cellComp.tsx - React 组件代理
const compProxy: ICellComp = {
toggleCss: (name, on) => cssManager.current!.toggleCss(name, on),
setUserStyles: (styles: CellStyle) => setUserStyles(styles),
getFocusableElement: () => eGui.current!,
setIncludeSelection: (include) => setIncludeSelection(include),
setIncludeRowDrag: (include) => setIncludeRowDrag(include),
setIncludeDndSource: (include) => setIncludeDndSource(include),
setRowResizerElement: (element) => {
if (rowResizerElement.current) {
_removeFromParent(rowResizerElement.current);
}
rowResizerElement.current = element;
if (element && eGui.current) {
eGui.current.appendChild(element);
}
},
getCellEditor: () => cellEditorRef.current ?? null,
getCellRenderer: () => cellRendererRef.current ?? jsCellRendererRef.current,
getParentOfValue: () => eCellValue.current ?? eCellWrapper.current ?? eGui.current,
setRenderDetails: (compDetails, value, force) => {
// React 状态更新
setRenderDetails((prev) => {
if (prev?.compDetails !== compDetails || prev?.value !== value || prev?.force !== force) {
return { value, compDetails, force };
}
return prev; // 无变化,返回原对象避免重渲染
});
},
// ...
};
避免不必要重渲染的策略
1. 引用保持
// utils.tsx - 维护引用避免重渲染
export function getNextValueIfDifferent<T extends { instanceId: string }>(
prev: T[] | null,
next: T[] | null,
maintainOrder: boolean
): T[] | null {
// 相同数组实例
if (prev === next || (next.length === 0 && prev.length === 0)) {
return prev;
}
// 如果所有值都相同但顺序不同,保持原引用
if (oldValues.length === prev.length && newValues.length === 0) {
return prev;
}
// ...
}
2. 状态更新优化
// cellComp.tsx - 仅在真正变化时更新状态
setRenderDetails: (compDetails, value, force) => {
const setDetails = () => {
setRenderDetails((prev) => {
// 深度比较避免不必要更新
if (prev?.compDetails !== compDetails || prev?.value !== value || prev?.force !== force) {
return { value, compDetails, force };
}
return prev;
});
};
// ...
}
3. React 18+ 的 startTransition
// cellComp.tsx - 使用 startTransition 降低渲染优先级
import { agStartTransition } from '../utils';
setRenderDetails: (compDetails, value, force) => {
if (compDetails?.params?.deferRender && !cellCtrl.rowNode.group) {
const { loadingComp, onReady } = cellCtrl.getDeferLoadingCellRenderer();
if (loadingComp) {
setRenderDetails({
value: undefined,
compDetails: loadingComp,
force: false,
});
// 使用 startTransition 使渲染可被中断
onReady.then(() => agStartTransition(setDetails));
return;
}
}
setDetails();
}
4. flushSync 控制
// utils.tsx - React 18+ flushSync 包装
export const agFlushSync = (useFlushSync: boolean, fn: () => void) => {
if (!isReactVersion17Minus && useFlushSync && !disableFlushSync) {
(ReactDOM as any).flushSync(fn);
} else {
fn();
}
};
// 在渲染周期内禁用 flushSync
export function runWithoutFlushSync<T>(func: () => T) {
if (!disableFlushSync) {
setTimeout(() => (disableFlushSync = false), 0);
}
disableFlushSync = true;
return func();
}
骨架单元格机制
SkeletonCellRenderer 实现
// skeletonCellRenderer.ts
export class SkeletonCellRenderer extends Component implements ILoadingCellRendererComp {
public init(params: ILoadingCellRendererParams): void {
if (params.deferRender) {
this.setupLoading(params);
} else if (params.node.failedLoad) {
this.setupFailed();
} else {
this.setupLoading(params);
}
}
private setupLoading(params: ILoadingCellRendererParams): void {
const skeletonEffect = _createElement({
tag: 'div',
cls: 'ag-skeleton-effect',
});
// 使用行索引生成伪随机宽度
const rowIndex = params.node.rowIndex;
if (rowIndex != null) {
// 基础 75% + [-25%, 25%] 变化
// 使用 sin/cos 交替产生伪随机效果,避免真正随机
const width = 75 + 25 * (rowIndex % 2 === 0 ? Math.sin(rowIndex) : Math.cos(rowIndex));
skeletonEffect.style.width = `${width}%`;
}
this.getGui().appendChild(skeletonEffect);
}
public refresh(_params: ILoadingCellRendererParams): boolean {
return false; // 骨架不刷新
}
}
React 端的骨架组件
// skeletonCellComp.tsx
export const SkeletonCellRenderer = ({
cellCtrl,
parent,
}: {
cellCtrl: CellCtrl;
parent: MutableRefObject<HTMLDivElement | null>;
}) => {
const jsCellRendererRef = useRef<ICellRendererComp>();
const renderDetails = useMemo(() => {
const { loadingComp } = cellCtrl.getDeferLoadingCellRenderer();
return loadingComp
? { value: undefined, compDetails: loadingComp, force: false }
: undefined;
}, [cellCtrl]);
// 使用 Hook 处理 JS 骨架渲染器
useJsCellRenderer(renderDetails, false, undefined, 1, jsCellRendererRef, parent);
// 如果是框架组件,直接渲染
if (renderDetails?.compDetails?.componentFromFramework) {
const CellRendererClass = renderDetails.compDetails.componentClass;
return <CellRendererClass {...renderDetails.compDetails.params} />;
}
return <></>;
};
作为 Suspense Fallback
// cellComp.tsx - Suspense 降级
<Suspense fallback={<SkeletonCellRenderer cellCtrl={cellCtrl} parent={eGui} />}>
<CellRendererClass {...compDetails.params} key={renderKey} />
</Suspense>
触发时机
- 滚动中创建单元格:当用户快速滚动时,延迟渲染复杂组件
- 异步数据加载:SSRM 模式下等待数据
- React Suspense:组件懒加载期间
延迟渲染策略
ColumnDelayRenderService
// columnDelayRenderService.ts
export class ColumnDelayRenderService extends BeanStub implements NamedBean {
beanName = 'colDelayRenderSvc' as const;
private hideRequested = false;
private alreadyRevealed = false;
private timesRetried = 0;
private readonly requesters = new Set<ColumnDelayRenderKey>();
public hideColumns(key: ColumnDelayRenderKey): void {
if (this.alreadyRevealed || this.requesters.has(key)) {
return;
}
this.requesters.add(key);
if (!this.hideRequested) {
this.beans.ctrlsSvc.whenReady(this, (p) => {
p.gridBodyCtrl.eGridBody.classList.add(HideClass);
});
this.hideRequested = true;
}
}
public revealColumns(key: ColumnDelayRenderKey): void {
if (this.alreadyRevealed || !this.isAlive()) {
return;
}
this.requesters.delete(key);
if (this.requesters.size > 0) {
return; // 还有其他请求者在等待
}
// 检查 Header 是否已渲染(React 特殊处理)
if (!renderStatus.areHeaderCellsRendered() && this.timesRetried < 5) {
this.timesRetried++;
setTimeout(() => this.revealColumns(key));
return;
}
ctrlsSvc.getGridBodyCtrl().eGridBody.classList.remove(HideClass);
this.alreadyRevealed = true;
}
}
CSS 延迟渲染
/* column-delay-render.css */
:where(.ag-delay-render) {
.ag-cell {
visibility: hidden;
}
.ag-row {
visibility: hidden;
}
.ag-spanned-cell-wrapper {
visibility: hidden;
}
.ag-header-cell {
visibility: hidden;
}
.ag-header-group-cell {
visibility: hidden;
}
}
滚动时延迟渲染
// cellCtrl.ts - 滚动时延迟加载
public getDeferLoadingCellRenderer(): {
loadingComp: UserCompDetails | undefined;
onReady: AgPromise<void>;
} {
const { ctrlsSvc, eventSvc } = this.beans;
if (ctrlsSvc.getGridBodyCtrl()?.scrollFeature?.isScrolling()) {
// 滚动中,返回等待滚动结束的 Promise
let resolver: () => void;
const onReady = new AgPromise<void>((resolve) => {
resolver = resolve;
});
const [removeBodyScrollEnd] = this.addManagedListeners(eventSvc, {
bodyScrollEnd: () => {
resolver();
removeBodyScrollEnd();
},
});
return { loadingComp, onReady };
}
// 非滚动状态,立即返回 resolved promise
return { loadingComp, onReady: AgPromise.resolve() };
}
动画帧任务调度
// cellComp.ts - 使用 AnimationFrameSvc 调度渲染
const { animationFrameSvc } = this.beans;
let createTask: ((details: UserCompDetails, isDeferred?: boolean) => void) | undefined;
if (animationFrameSvc?.active && this.firstRender) {
// 首次渲染使用任务队列
createTask = (details, isDeferred = false) => {
animationFrameSvc.createTask(
createCellRendererFunc(details),
this.rowNode.rowIndex!,
'p2', // 优先级
details.componentFromFramework,
isDeferred
);
};
} else {
createTask = (details: UserCompDetails) => createCellRendererFunc(details)();
}
值缓存机制
ValueCache 实现
// valueCache.ts
export class ValueCache extends BeanStub implements NamedBean {
beanName = 'valueCache' as const;
private cacheVersion = 0;
private active: boolean;
private neverExpires: boolean;
public postConstruct(): void {
this.active = this.gos.get('valueCache');
this.neverExpires = this.gos.get('valueCacheNeverExpires');
}
public onDataChanged(): void {
if (this.neverExpires) {
return;
}
this.expire();
}
public expire(): void {
this.cacheVersion++; // 版本号递增使旧缓存失效
}
public setValue(rowNode: RowNode, colId: string, value: any): any {
if (this.active) {
const cacheVersion = this.cacheVersion;
// 版本不匹配时重置缓存
if (rowNode.__cacheVersion !== cacheVersion) {
rowNode.__cacheVersion = cacheVersion;
rowNode.__cacheData = {};
}
rowNode.__cacheData[colId] = value;
}
}
public getValue(rowNode: RowNode, colId: string): any {
if (!this.active || rowNode.__cacheVersion !== this.cacheVersion) {
return undefined; // 缓存未激活或已过期
}
return rowNode.__cacheData[colId];
}
}
缓存策略总结
| 配置选项 | 效果 |
|---|---|
valueCache: true | 启用值缓存 |
valueCacheNeverExpires: true | 数据变化时不清除缓存 |
缓存生命周期
数据变化 → onDataChanged() → expire() → cacheVersion++
↓
后续 getValue() 返回 undefined
↓
setValue() 重新填充缓存
单元格回收与复用
虚拟化基础
AG Grid 采用行虚拟化,只渲染可视区域内的行。单元格的创建和销毁由 RowCtrl 管理:
// cellCtrl.ts - 单元格重建条件
public refreshOrDestroyCell(params?: RefreshCellsParams): void {
if (this.refreshShouldDestroy()) {
this.rowCtrl?.recreateCell(this); // 销毁并重建
} else {
this.refreshCell(params); // 仅刷新
}
// 编辑状态恢复
if (this.hasEdit && this.editCompDetails) {
if (!comp?.getCellEditor() && editSvc!.isEditing(this, { withOpenEditor: true })) {
editSvc!.startEditing(this, { startedEdit: false, source: 'api', silent: true });
}
}
}
private refreshShouldDestroy(): boolean {
const colDef = this.column.getColDef();
const selectionChanged = this.includeSelection != this.isIncludeControl(this.isCheckboxSelection(colDef), true);
const rowDragChanged = this.includeRowDrag != this.isIncludeControl(colDef.rowDrag);
const dndSourceChanged = this.includeDndSource != this.isIncludeControl(colDef.dndSource);
const autoHeightChanged = this.isAutoHeight != this.column.isAutoHeight();
return selectionChanged || rowDragChanged || dndSourceChanged || autoHeightChanged;
}
组件复用策略
// cellComp.ts - 渲染器复用逻辑
private setRenderDetails(
compDetails: UserCompDetails | undefined,
valueToDisplay: any,
forceNewCellRendererInstance: boolean
): void {
// 如果 display template 变化,需要重建
const controlWrapperChanged = this.refreshWrapper(false);
if (compDetails) {
const neverRefresh = forceNewCellRendererInstance || controlWrapperChanged;
const cellRendererRefreshSuccessful = neverRefresh ? false : this.refreshCellRenderer(compDetails);
if (!cellRendererRefreshSuccessful) {
this.destroyRenderer();
this.createCellRendererInstance(compDetails);
}
} else {
this.destroyRenderer();
this.insertValueWithoutCellRenderer(valueToDisplay);
}
}
其他性能技巧
1. CSS transform 代替 top/left(隐式)
AG Grid 的行定位策略:
// 通过 positionFeature 管理位置
this.positionFeature = new CellPositionFeature(this, beans);
// 列宽变化时的处理
public onWidthChanged(): void {
return this.positionFeature?.onWidthChanged();
}
// 左侧位置变化
public onLeftChanged(): void {
this.positionFeature?.onLeftChanged();
}
2. 批量 CSS 类操作
// cellComp.ts - CssClassManager
this.cellCssManager = new CssClassManager(() => cellDiv);
// 代理方法
const compProxy: ICellComp = {
toggleCss: (cssClassName, on) => this.cellCssManager.toggleCss(cssClassName, on),
// ...
};
3. 智能 ARIA 属性更新
// cellCtrl.ts - 仅在必要时更新 ARIA
public refreshAriaRowIndex(): void {
if (!isRowNumberCol(this.column) || !this.eGui) {
return;
}
const { ariaRowIndex } = this.rowCtrl;
if (ariaRowIndex != null) {
_setAriaRowIndex(this.eGui, ariaRowIndex);
}
}
4. 自动宽度计算优化
// autoWidthCalculator.ts - 克隆测量技巧
public getPreferredWidthForColumn(column: AgColumn, skipHeader?: boolean): number {
// 创建虚拟表单元素避免表单验证
const eDummyContainer = document.createElement('form');
eDummyContainer.style.position = 'fixed';
// 克隆单元格
for (const el of elements) {
this.cloneItemIntoDummy(el, eDummyContainer);
}
// 添加到 DOM 后测量
eBodyContainer.appendChild(eDummyContainer);
const dummyContainerWidth = Math.ceil(eDummyContainer.getBoundingClientRect().width);
// 立即移除
eDummyContainer.remove();
return dummyContainerWidth + extraPadding;
}
5. 实例 ID 生成
// cellCtrl.ts - 唯一 ID 用于 React key
let instanceIdSequence = 0;
export class CellCtrl extends BeanStub {
public readonly instanceId: CellCtrlInstanceId;
constructor(...) {
// 包含列 ID 便于调试
this.instanceId = (colId + '-' + instanceIdSequence++) as CellCtrlInstanceId;
}
}
6. 无组件渲染器的状态判断
// cellComp.tsx - 初始状态优化
const [renderDetails, setRenderDetails] = useState<RenderDetails | undefined>(() =>
cellCtrl.isCellRenderer()
? undefined // 有渲染器时不设置初始状态,避免闪烁
: { compDetails: undefined, value: cellCtrl.getValueToDisplay(), force: false }
);
7. 无状态组件检测
// utils.tsx - 检测函数组件和 memo
export const isComponentStateless = (Component: any) => {
const hasSymbol = () => typeof Symbol === 'function' && Symbol.for;
const getMemoType = () => (hasSymbol() ? Symbol.for('react.memo') : 0xead3);
return (
(typeof Component === 'function' && !(Component.prototype && Component.prototype.isReactComponent)) ||
(typeof Component === 'object' && Component.$$typeof === getMemoType())
);
};
总结
AG Grid 的单元格渲染性能优化是一个多层次、多维度的系统工程:
| 优化维度 | 核心技术 |
|---|---|
| 渲染策略 | React/JS 渲染器分离、Suspense、startTransition |
| 更新策略 | 值比较、刷新机制、版本控制 |
| DOM 优化 | 最小操作、按需创建、textContent vs appendChild |
| 桥接层 | ICellComp 接口、状态优化、引用保持 |
| 骨架机制 | 伪随机宽度、滚动延迟、Suspense fallback |
| 延迟渲染 | CSS visibility、AnimationFrame 调度、滚动检测 |
| 值缓存 | 版本化缓存、自动过期、按需填充 |
| 虚拟化 | 行虚拟化、条件重建、编辑状态恢复 |
这些优化共同构成了 AG Grid 能够流畅渲染数百万行数据的技术基础。