@scvzerng/ag-grid-core
v0.1.1
Published
Core business logic for ag-grid based table components (model / state / data source). Framework-agnostic via composition API subset.
Readme
@scvzerng/ag-grid-core
ag-grid 业务封装核心包 — 合并单元格 + 树展开 + 编辑流 + 选中同步
monorepo 根:
element-plus-ag-grid/公共 API 100% 兼容,跨 vue 2.7+ / vue 3 通用
简介
@scvzerng/ag-grid-core 封装 ag-grid 社区版的"合并单元格"业务场景:
- SpanMatrix: 纯数据模型, 不知道 ag-grid / vue 存在
data + columns → span tree, 支持任意嵌套层级- 增量更新 API (setRow / setArrayLength)
- O(1) span 查询 (getCellSpan)
- 行模型:
ActualRow/EditableRow/CellEditableRow/EditSession- 透出 ag-grid
IRowNode兼容的GridRowinterface - 编辑流 (row mode / cell mode)
- 透出 ag-grid
- Vue hook:
useGrid(vue 2.7+ / vue 3 通用)- 列状态持久化 (useGridState)
- 事件代理 (buildProxyOptions)
- 类型守卫:
isGridRow/isActualRow
业务方能独立用 core: 只想算 span / 数据查询, 不需要 Grid 组件, 也能直接用 SpanMatrix / RowDataModel.
安装
# monorepo 内
pnpm add @scvzerng/ag-grid-core --filter <your-package>
# 外部使用
pnpm add @scvzerng/ag-grid-core ag-grid-community@^34公共 API
1. 纯数据模型 (无 ag-grid / vue 依赖)
| 导出 | 用途 |
|---|---|
| SpanMatrix | 树展开 + span 计算 (核心) |
| SpanNode (type) | span 树节点 |
| SpanColumn (type) | 列定义 ({ colId, cellDataType? }) |
| SetRowContext (type) | setRow 上下文 ({ columns }) |
| CellValue | 单 cell 值 (含 span 信息) |
| RowSpans | CellValue span cache 代理 |
| RowDataModel | 单行数据查询 (Phase 2 抽离) |
| ProjectionRow | 投影行 (合并行分裂的子行) |
| GridRow (type) | 行模型 interface |
| isGridRow / isActualRow | 类型守卫 |
2. 行模型 (ag-grid runtime 抽象)
| 导出 | 用途 |
|---|---|
| ActualRow | 真实行 (thin facade, 数据查询委派 RowDataModel) |
| EditableRow | 整行编辑 (row mode) |
| CellEditableRow | 单 cell 编辑 (cell mode) |
| EditSession (abstract) | Phase 5 抽离的公共基类 (业务方一般不直接用) |
| GridDataCache | 行容器 (支持 replace / insertRows / deleteRows) |
3. Vue hook (vue 2.7+ / vue 3 通用)
| 导出 | 用途 |
|---|---|
| useGrid | 业务主 hook, 返回 gridOptions / dataCache / gridApi / gridElRef / reload / startEditing / stopEditing / insertRows / existRow / getRows / refresh |
| useGridState | 列状态持久化 (列宽 / 排序 / 列顺序), 持久化通过回调注入 |
| useGridScroll | 滚动到底加载更多 |
| useFooter | 表格底部 footer |
| ServerGetRowsParamsRequest | 分页请求参数构造 |
| RequestErrorCallback (type) | 错误回调类型 |
| UseGridProps (type) | useGrid props 类型 |
| UseFooterProps (type) | useFooter props 类型 |
4. 事件代理 (UI 库回调注入)
| 导出 | 用途 |
|---|---|
| buildProxyOptions | ag-grid 事件代理 (hover 同步 / cell 点击切编辑 / row 选中同步 / 合并行 row class 注入) |
| ProxyCtx (type) | buildProxyOptions 参数 |
| InstanceTraversalCtx (type) | 实例遍历上下文 |
| RefLike (type) | vue Ref 类型兼容抽象 ({ value: T }) |
5. 列 class rules 辅助
| 导出 | 用途 |
|---|---|
| selectionColumnCellClassRules | 选中列的 cell class rules |
| defaultColDefCellClassRules | 默认列的 cell class rules |
| mergeRowClass | 合并多 row class 规则 |
6. 业务模块 (可选)
| 导出 | 用途 |
|---|---|
| RowSelectionModule / SharedRowSelectionModule | ag-grid 行选中模块 |
| ClientSideRowModelModule / ClientSideRowModelApiModule | ag-grid 客户端行模型模块 |
7. 数据源 / 工具
| 导出 | 用途 |
|---|---|
| GridDataSource | 数据源 (支持静态 / 分页异步) |
| DataGetter (type) | 数据获取函数 |
| PageFetchParams / FetchParams (type) | 分页参数 |
| SortModel (type) | 排序模型 |
| GridUtils | 工具函数集合 |
| ID_SPLITTER / ID_FIRST_DEEP (常量) | ID 切分常量 |
| _getSortedColumns 等 | 列排序工具 |
用法示例
1. 独立使用 SpanMatrix (不依赖 vue / ag-grid)
典型场景: 后端 / 业务侧只想算 span 树, 不需要渲染。
import { SpanMatrix } from '@scvzerng/ag-grid-core'
// 1. 准备数据 (任意嵌套层级)
const data = {
id: '0',
name: '高 0 班',
teachers: [
{ name: '张老师', students: [{ name: '学生甲' }, { name: '学生乙' }] },
{ name: '李老师', students: [{ name: '学生丙' }] },
],
}
// 2. 定义列 (colId 用 . 分隔 path, array path 自动展开)
const columns = [
{ colId: 'name' }, // 班级名
{ colId: 'teachers.name' }, // 任课老师名 (展开)
{ colId: 'teachers.students.name' }, // 任课老师的学生 (嵌套展开)
]
// 3. 创建 SpanMatrix
const matrix = new SpanMatrix({ 0: data }, columns)
// 4. 查询每个 cell 的 span
const rootLeaves = matrix.getLeaves('0')
// returns SpanNode[] - 树状结构, 每节点含 id / value / spanRow / path / nestingIndexes
// 5. 查某 cell 的 span (rowSpan 值)
const span = matrix.getCellSpan('0', 0, 'teachers.name') // 0: 第一行, col: teachers.name
// returns 1 (1 行) 或 2 (合并 2 行)
// 6. 增量更新: 数组长度变化
matrix.setArrayLength('0', 'teachers', 5) // teachers 数组变成 5 个
// 内部只重算 parent 子树, 不重建整行2. 独立使用 RowDataModel (查询 + 增量更新)
典型场景: 想从行 + 列拿到 cell value, 不想直接碰 ag-grid。
import { RowDataModel } from '@scvzerng/ag-grid-core'
// RowDataModel 是行级别的纯数据模型
// 业务方一般通过 ActualRow 间接用, 但也能直接 new
const data = {
id: '0',
name: '高 0 班',
teachers: [{ name: '张老师' }],
}
const columns = [
{ colId: 'name' },
{ colId: 'teachers.name' },
]
const model = new RowDataModel('0', data, columns)
// 查 cell value (走 SpanMatrix.getCell)
const cell = model.getCell({ getColId: () => 'name' } as any, [])
// cell.value === '高 0 班'
// 增量更新
model.setArrayLength('teachers', 3) // 增 2 个 teacher
// 整行重建 (当 data 整体改时)
model.rebuild({ ...data, name: '改名' })3. 类型守卫 (替代 instanceof)
import { isGridRow, isActualRow } from '@scvzerng/ag-grid-core'
function handleRow(row: any) {
if (isGridRow(row)) {
// row 满足 GridRow interface
row.getId() // OK
row.getData() // OK
}
if (isActualRow(row)) {
// row 进一步缩窄到 ActualRow
row.startEditing() // OK
row.delete() // OK
}
}4. 完整 Grid 集成 (走 useGrid + vue adapter)
<script setup lang="ts">
import { useGrid } from '@scvzerng/ag-grid-core'
import { ElMessage } from 'element-plus' // 或 element-ui
interface Class {
id: string
name: string
teachers: { name: string }[]
}
const props = defineProps<{
data: Class[]
}>()
const {
gridOptions, // 给 ag-grid 用
dataCache, // 行容器
gridApi, // ag-grid api
gridElRef, // 容器 ref
reload, // 重新加载
startEditing, // 进入行编辑
stopEditing, // 退出行编辑
insertRows, // 插入新行
getRows, // 拿所有行
} = useGrid<Class>({
data: props.data,
rowIdMapper: (row) => row.id, // 必填, 唯一 id
onError: (msg) => ElMessage.error(msg), // 错误消息回调
onMessageError: (msg) => ElMessage.error(msg), // use-proxy 内部错误
})
</script>
<template>
<ag-grid-vue
:gridOptions="gridOptions"
:modules="modules"
style="width: 100%; height: 600px"
/>
</template>5. 事件代理 (buildProxyOptions)
import { buildProxyOptions } from '@scvzerng/ag-grid-core'
const proxyOptions = buildProxyOptions({
options: userGridOptions,
elRef: gridElRef,
editable: 'cell', // 'cell' / 'row' / undefined
onRowChange: async (row) => {
// cell 模式下保存草稿
await saveToServer(row)
},
onMessageError: (msg) => ElMessage.error(msg),
})buildProxyOptions 做的事:
onCellMouseOver/onCellMouseOut: 合并行同步 hover (cell-level +event.relatedTarget判断同 rootId 兄弟)onCellClicked: cell 模式 → 切 cell 编辑onRowSelected: 选中一行同步勾选它的所有合并行 (用getRootId === rootId严格匹配)getRowClass: 注入row_${rootId}(合并行查询需要)onRowDoubleClicked: row 模式 → 进整行编辑
跟 vue2 / vue3 adapter 的关系
element-plus-ag-grid (vue 3 + element-plus)
└─ Grid.vue (vue3 wrapper)
└─ useGrid() (core) ← 你在这里
└─ buildProxyOptions() ← 你在这里
└─ ElMessage.error ← 注入 UI 回调
element-ui-ag-grid-vue2 (vue 2.7+ + element-ui)
└─ Grid.vue (vue2 wrapper)
└─ useGrid() (core) ← 同一个 useGrid
└─ buildProxyOptions() ← 同一个 buildProxyOptions
└─ Message.error (element-ui) ← 注入 UI 回调core 不依赖 UI 库: 错误消息 / 弹框 / 通知都通过回调 (onError / onMessageError / InstanceTraversalCtx) 注入, vue2 / vue3 adapter 各自接 element-ui / element-plus.
高级用法
SpanMatrix 增量更新
setArrayLength 是 O(被影响叶子数), 跟 setRow 全量重建对比:
// 假设一行有 1000 个嵌套元素
// 改一个 array 长度 (1 -> 5), 受影响叶子数 = 4 (新增 4 个)
// 老实现: 全量 rebuild, O(1000) cell span 重算
// 新实现: setArrayLength 增量, O(4) parent 子树重算
// 性能: 250x 改进
matrix.setArrayLength('0', 'students', 5)详见 docs/SPAN_MATRIX_MIGRATION.md §7 性能对比。
自定义 useGridState 持久化
import { useGridState } from '@scvzerng/ag-grid-core'
const { columnState, onColumnStateChanged } = useGridState({
// 业务方注入存储后端
save: async (state) => {
await localStorage.setItem('grid-state', JSON.stringify(state))
},
load: async () => {
const json = await localStorage.getItem('grid-state')
return json ? JSON.parse(json) : null
},
})类型定义扩展 (CellDataType)
import type { SpanColumn } from '@scvzerng/ag-grid-core'
const columns: SpanColumn[] = [
{ colId: 'name' }, // 字符串列 (按 data 形状自动推断)
{ colId: 'amount', cellDataType: 'number' },
{ colId: 'createdAt', cellDataType: 'date' },
{ colId: 'meta', cellDataType: 'native' }, // native: 跨整组 (不展开)
]文档
docs/REDESIGN.md(24KB) - 6-phase 蓝图, 整体设计理念docs/SPAN_MATRIX_MIGRATION.md(26KB) - Phase 1 详细迁移日志docs/PHASE2_ROW_DATA_MODEL.md(8KB) - Phase 2 RowDataModel 抽离docs/PHASE3_HOVER_SELECT.md(7KB) - Phase 3 hover/select 同步docs/PHASE5_EDIT_SESSION.md(12KB) - Phase 5 EditSession 抽离docs/EXAMPLES.md(Phase 6) - 常见合并场景示例docs/PERFORMANCE.md(Phase 6) - 性能对比报告
公共 API 兼容性
@scvzerng/ag-grid-core 公共 API 100% 兼容 (类名 + 方法签名):
SpanMatrix.setRow/setArrayLength/getCellSpan/getCellPath/getMaxArrayLength/getLeaves/getDirtyRoots/clearDirtyActualRow.startEditing/startEditingCell/redraw/redrawCell/delete/getId/getData/isEditing/isCellEditing/flatEditableRow.persistent/isTransientGridDataCache.getRows/setRows/appendRows/insertRows/remove/deleteRows/createRow/replaceuseGrid/useGridState/useGridScroll/useFooterbuildProxyOptions
每次重构保持公共方法签名不变, 改的是内部实现。
测试
175 tests 全过 (从 monorepo 根跑 pnpm -r test):
56 SpanMatrix
16 RowDataModel
25 render-integration (Phase 1 7 个集成 bug 回归)
22 use-proxy (9 核心 + 13 边界)
56 EditSession (29 核心 + 27 边界)License
MIT
