AG Grid 源码分析:单元格渲染与复杂数据处理

2026/3/28
AG GridReact单元格渲染性能优化前端

AG Grid 单元格渲染性能优化机制深度分析

目录

  1. 架构概览
  2. React 组件 vs JS 渲染器选择策略
  3. 单元格更新策略
  4. DOM 操作优化
  5. React 与 AG Grid 桥接层
  6. 骨架单元格机制
  7. 延迟渲染策略
  8. 值缓存机制
  9. 单元格回收与复用
  10. 其他性能技巧

架构概览

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
CellCompDOM 层组件,管理原生 DOM 操作cellComp.ts
CellComp (React)React 组件,协调 React 渲染cellComp.tsx
showJsRendererJS 渲染器桥接 HookshowJsRenderer.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 === trueReact 组件支持 Suspense、React 生命周期
componentFromFramework === falseJS 渲染器由 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
  • 列定义中无 fieldvalueGettershowRowGroup(无法判断值变化)

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>

触发时机

  1. 滚动中创建单元格:当用户快速滚动时,延迟渲染复杂组件
  2. 异步数据加载:SSRM 模式下等待数据
  3. 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 能够流畅渲染数百万行数据的技术基础。

📝 文章反馈