44-useRowSet:220行镜像后端的行集实现
前端useRowSet.ts与后端Row.java是同一套语义的两种语言实现------_t/_o/三缓冲/collect/accept/reject一字不差。但这220行不是翻译------Vue3的reactive让"改字段界面自动更新"免费获得。这篇逐段对照Java版讲TypeScript实现,重点在差异处。
文章目录
源码:
browise-vue/src/core/useRowSet.ts(220行)测试:
useRowSet.test.ts(25个用例)
一、Row类:与Java版的逐行对照
typescript
class Row {
_data: RowData // Java版: LinkedHashMap data
_t: RowStatus = RowStatus.NONE // 同名
_o: RowData = {} // 同名
constructor(data?: RowData) {
this._data = reactive(data ? { ...data } : {}) // ★关键差异
}
setItemValue(key: string, value: any): void {
if (this._t === RowStatus.NONE) {
if (this._o[key] === undefined && this._data[key] !== undefined) {
this._o[key] = this._data[key] // 首改记旧值
}
this._t = RowStatus.UPDATE
}
this._data[key] = value
}
}
与Row.java的差异三处:
| Java版(第17篇) | TS版 | |
|---|---|---|
| 数据载体 | LinkedHashMap | reactive({})------Vue响应式代理 |
| _o记录条件 | containsKey即记 |
undefined判断(TS没有null Map语义) |
| 额外能力 | getMap返回引用 | clone()、toRaw()浅拷贝 |
reactive是最大差异也是最大免费午餐 ------this._data[key] = value这行在Java里只是改Map,在Vue3里自动触发依赖此字段的组件重渲染 。不需要手写"字段变了通知界面"------表单控件绑了_data.name,改值输入框自动更新。脏标记与响应式两层追踪并存:响应式管"界面刷新",_t/_o管"提交什么"。
二、remove的INSERT分岔:语义完全一致
typescript
function remove(row: Row): void {
const idx = buffer.primary.indexOf(row)
if (idx >= 0) {
buffer.primary.splice(idx, 1)
if (row._t === RowStatus.INSERT) {
return // INSERT直接删完事
}
row._t = RowStatus.DELETE
buffer.delete_.push(row) // 非INSERT进delete缓冲
}
}
与Java版第18篇一模一样的分岔 ------INSERT行删除不进delete_缓冲。delete_的尾下划线是TS命名(delete是保留字)。
三、collect的三模式:比Java版多两个
typescript
function collect(mode: 'auto' | 'all' | 'dirty' = 'auto'): RowData[] {
if (mode === 'all') return primary.map(r => r.toRaw()) // 纯数据
if (mode === 'dirty') {
// primary的脏行 + delete_的全部行,带_t/_o
const raw = { ...r.toRaw(), _t: r._t }
if (Object.keys(r._o).length > 0) raw._o = { ...r._o }
result.push(raw)
}
// auto(默认)= primary全部带_t(不含_o)
return primary.map(r => ({ ...r.toRaw(), _t: r._t }))
}
| 模式 | 内容 | 场景 |
|---|---|---|
| auto | 全部行+_t | 常规提交(后端按_t分发) |
| all | 纯数据(无_t/_o) | 导出/打印快照 |
| dirty | 脏行+_t/_o | commonSave提交报文(第23篇) |
Java版只有一种collect(=dirty)------TS版多了auto/all是前端场景更杂(导出要all、调试要auto)。
_o的条件附加 ------if (Object.keys(r._o).length > 0)------空_o不输出,报文瘦身(新增行根本没有_o,别传个空对象占字节)。
四、rejectChanges:TS版比Java版多做了一件事
typescript
function rejectChanges(): void {
for (let i = primary.length - 1; i >= 0; i--) { // 倒序删------与Java同
const r = primary[i]
if (r._t === RowStatus.INSERT) {
primary.splice(i, 1)
} else if (r._t === RowStatus.UPDATE) {
for (const key of Object.keys(r._o)) {
r._data[key] = r._o[key] // ★逐字段恢复------不是clear+putAll
}
r._t = RowStatus.NONE
r._o = {}
}
}
// ★delete_缓冲的行回归primary------Java版没有!
for (const r of buffer.delete_) {
r._t = RowStatus.NONE
r._o = {}
buffer.primary.push(r)
}
buffer.delete_ = []
}
两处与Java版的差异:
①逐字段恢复 vs 整体替换 ------Java版(修复后)用clear+putAll(originals)整体替换;TS版逐字段回填_o的key。语义等价(_o只含改过的字段,没改的字段_data值本来就是原值)------TS版的逐字段更符合直觉,且不会误删_o之外的字段。
②delete_回归primary ------Java版直接clear丢弃(第19篇讲过这是已知边界) ,TS版把删除的行恢复回primary !前端实现反而更完整------撤销删除在界面上是合理预期(用户删错了点取消,行该回来)。这是前后端行为差异的已知点------后端reject场景少(服务端批处理失败直接回滚事务),前端reject是用户高频操作。
五、25个单测锁定的行为契约
useRowSet.test.ts(25用例)------前后端行为等价的保障:
- add标记INSERT / remove的INSERT直删 / remove的DELETE入缓冲
- setItemValue首改记_o、再改不覆盖
- collect三模式的内容正确性
- accept清_o / reject恢复 / reject删除行回归
- clone的_t/_o深拷贝
这些用例的断言与Java版BaseEntityTest/RowSetTest的断言语义一致 ------测试是协议的真正文档。前后端各自改实现可以,测试断言改了就说明协议变了------必须两头同步。
六、setData与clear:查询结果的灌入
typescript
function setData(rows: RowData[]): void {
clear()
for (const data of rows) {
buffer.primary.push(new Row(data))
}
}
对应Java版reset------HTTP查询回来灌入 (httpClient的pageQuery→response.data.rows→setData)。clear用splice(0)而非length=0------保持reactive数组的引用(Vue3的响应式对替换引用会丢失依赖)。
✅ 亮点:TS与Java版的三处差异(reactive/_o条件/clone)、collect三模式比Java多的场景、reject的delete_回归是前端更完整的实现、25个单测作为协议真正文档、splice(0)保持响应式引用的细节。与第17-19篇形成前后端镜像闭环。适合实现前端数据层的人。扩展方向:第45篇useBinding、第46篇useCenter、第02篇设计哲学。