随心撤销重做:visual-json History类实现undo/redo机制源码走读

发布时间:2026/10/23 4:13:51

随心撤销重做:visual-json History类实现undo/redo机制源码走读 【免费下载链接】visual-jsonThe Visual JSON Editor. Schema-aware, embeddable, extensible.项目地址https://gitcode.com/gh_mirrors/vis/visual-json点击查看免费下载visual-json 是一款支持 Schema 感知、可嵌入、可扩展的可视化 JSON 编辑器The Visual JSON Editor而它的 undo/redo撤销/重做能力核心就来自 visual-json/core 包里的一个仅 42 行的History类。本文将带你走读 history.ts 的源码看看它如何用单栈 游标的经典设计让编辑器的每一步操作都能随心撤销和重做。 为什么 JSON 编辑器离不开 undo/redo在 JSON 编辑器里用户每天都在做这类操作修改某个字段的值增删、重命名属性拖拽移动、调整数组顺序批量变更节点类型一旦误删了一个节点没有撤销功能就意味着满盘皆输。visual-json 把这类需求抽象得非常干净所有树操作都是不可变的immutable每次操作都会产出一个全新的TreeState而History类只需要负责存档这些快照、并在前后翻动。这种存状态而非存操作的思路是本文源码走读的第一个重点 ✨ 核心源码走读History 类只有 42 行History类定义在 packages/visual-json/core/src/history.ts并通过 index.ts 从visual-json/core对外导出。整个类只有两个私有字段export class History { private stack: TreeState[] []; private cursor -1; // ... }这就是经典的单栈 游标模型成员含义stack存放所有历史快照TreeState的数组cursor指针标记当前所在的历史位置初始为 -1还没有任何状态理解了这个指针undo 和 redo 就一目了然了 单栈 游标undo/redo 一步到位很多初学者会以为撤销重做需要两个栈一个 undo 栈、一个 redo 栈。visual-json 只用了一个数组数组cursor之前的部分 可以撤销的历史数组cursor之后的部分 可以重做的未来undo(): TreeState | null { if (!this.canUndo) return null; this.cursor--; return this.stack[this.cursor]; } redo(): TreeState | null { if (!this.canRedo) return null; this.cursor; return this.stack[this.cursor]; }undo 就是游标减一redo 就是游标加一源码见 history.ts#L19-L29。两个能力判断同样简单get canUndo(): boolean { return this.cursor 0; } get canRedo(): boolean { return this.cursor this.stack.length - 1; }canUndo/canRedo这两个 getter 会直接驱动 UI 上撤销/重做按钮的可用状态——这正是编辑器体验流畅的关键细节。push() 方法剪掉 redo 分支 容量上限push是History里逻辑最密集的方法一共四行核心代码push(state: TreeState): void { this.stack this.stack.slice(0, this.cursor 1); this.stack.push(state); if (this.stack.length MAX_HISTORY) { this.stack.shift(); } else { this.cursor; } }拆开看它做了三件事剪掉 redo 分支slice(0, this.cursor 1)会丢弃游标之后的所有未来状态。这符合直觉——撤销两步之后又做了新操作原来那条未来路径就应该作废想象 Git 里一次新的 commit 会让旧分支失焦。压入新快照把最新的TreeState追加到栈尾。容量保护栈超过MAX_HISTORY 100时shift()挤掉最老的一条。这里有个精妙之处——超限走shift()分支时不加cursor因为数组整体前移了一位游标的数值不变却自动指向了最新状态正常分支才需要cursor。 100 条快照的默认上限是一个很好的工程权衡既能覆盖绝大多数编辑场景又不会让内存随编辑时长无限增长。 框架层如何用 History以 React 为例History本身是纯 TS 类不依赖任何框架React / Vue / Svelte 三个封装包共用同一套逻辑。以 React 封装 visual-json.tsx 为例用法非常直接持有实例用useRefHistory(new History())保存跨渲染不重建每次变更就存档setTree更新树状态后立即historyRef.current.push(newTree)并同步canUndo/canRedo到 state 供 UI 使用见 visual-json.tsx#L123-L133undo/redo 回调拿到返回的TreeState后直接setTreeState并通过onChange通知外部见 visual-json.tsx#L135-L155快捷键开箱即用Ctrl/Cmd Z撤销、Shift Ctrl/Cmd Z或Ctrl Y重做见 visual-json.tsx#L157-L174外部值变化时重置历史如果外部valueprop 变了会新建一个History并只压入初始快照保证历史栈不被跨文档污染见 visual-json.tsx#L113-L116。Vue 与 Svelte 封装也是同样的模式VisualJson.vue 与 VisualJson.svelte 里同样各持有一个new History()实例。 测试用例里藏着哪些设计细节官方为History写了 10 个测试用例见 history.test.ts其中有三个用例特别值得新手学习undo 之后 push 会丢弃 redo 栈L74-L90push 三次后 undo 再 push此时canRedo必须为false而继续 undo 一步回到的是最初状态而非被剪掉的状态——这正是slice剪枝行为的直接验证。超限裁剪后 current 依然正确L105-L135连续 push 101 次最老的快照被挤掉可撤销次数是 99 而不是 100current始终指向最新状态。空栈边界初始状态下undo()与redo()都安全地返回nullcurrent也为null不会抛错L15-L20。这套测试把游标越界、分支剪枝、容量裁剪三个最容易出 bug 的角落全部覆盖了是学习状态机类单元测试的绝佳范本。 想继续深入核心源码packages/visual-json/core/src/history.ts单元测试packages/visual-json/core/src/tests/history.test.tsTreeState类型定义packages/visual-json/core/src/types.ts#L24-L27Core API 文档含 History 用法示例apps/web/src/app/docs/core/page.mdx#L69-L83React 封装packages/visual-json/react/src/visual-json.tsx总结visual-json 的History类用 42 行代码演示了一个教科书级的 undo/redo 实现不可变树状态 快照栈存状态而非操作撤销重做天然可逆、零兼容成本单栈 游标undo/redo 各一行核心逻辑canUndo/canRedo直接驱动 UIpush 剪枝 100 条上限处理了撤销后新操作的分支问题和内存增长问题框架无关React、Vue、Svelte 三端复用同一个类行为完全一致。下次当你按下 Ctrl Z 时背后就是这段游标轻轻移动了一格。希望这篇源码走读能帮你把 undo/redo 的设计彻底想明白赞分享【免费下载链接】visual-jsonThe Visual JSON Editor. Schema-aware, embeddable, extensible.项目地址https://gitcode.com/gh_mirrors/vis/visual-json点击查看免费下载相关推荐Fluid Framework 中的 Undo/Redo 实现原理分布式协作的撤销重做机制详解Fluid Framework 中的 Undo/Redo 实现原理分布式协作的撤销重做机制详解 Fluid Framework 是一个用于构建分布式实时协作后端前端为 three.js Editor 的撤销/重做Undo/Redo扩展自定义命令Command为 three.js Editor 的撤销/重做Undo/Redo扩展自定义命令Command 本指南以 three.js 官方编辑器editor自3D渲染图形学前端TinyBase Checkpoints 使用指南为 Store 构建撤销 / 重做Undo / Redo能力TinyBase Checkpoints 使用指南为 Store 构建撤销 / 重做Undo / Redo能力 Checkpoints 是 TinyBas数据库后端前端上一篇VMAF特征选择算法使用递归特征消除优化模型输入维度下一篇PP-MobileSeg面向移动端的高效语义分割模型训练与部署实战PaddleSeg 实现创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/23 4:13:51

Spirula Studio 入门指南:一个二进制跑通 3D 高斯泼溅训练

Spirula Studio 入门指南:一个二进制跑通 3D 高斯泼溅训练 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Spi…

2026/10/23 5:13:59

同花顺Level-2行情SDK实战:涨停板策略与逐笔委托解析

简介:《同花顺level-2全推行情Python API操作手册》面向从事高频交易、量化投资及涨停板策略的证券开发者,系统讲解如何通过Python API获取并处理沪深市场Level-2高频行情数据。手册围绕TDS系统的C/S架构展开,涵盖五档与十档行情快照、逐笔成…

2026/10/23 5:13:59

Pandas打造博客质量评分系统:对唯流量论说不

1. 为什么“唯流量论”会误导内容运营:从后台数据看内容价值的失真做博客和自媒体内容的朋友,应该都有过这样的经历:后台数据里一篇阅读量很高的文章,评论区却冷冷清清,读者平均停留时间极短,基本是标题吸引…

2026/10/23 5:13:59

量化策略回测分析框架:Python因子建模与参数优化全流程

gsq-rco 量化最近讨论热度不低,很多人关心它为什么能做到相对稳定、回撤可控。这类问题其实很难用一两句“玄学”解释清楚,更合理的做法是把一个量化策略拆成因子、模型、交易执行、风控四层,逐层看它强在什么地方。本文不猜具体代码&#xf…

2026/10/23 5:13:59

深度伪造检测攻防实战:从生成式AI伪影到可落地的检测流水线

前几天接到一个非常急的协助请求:某个社交平台上一段视频里出现某公众人物,画面清楚、声音也对,但运营团队总觉得哪里不对劲。我打开视频,第一遍也没看出问题,直到逐帧停在某个侧脸角度,才发现眼窝周围的光…

2026/10/23 5:13:59

1.58位大模型:三值量化如何让权重只剩-1、0、1并加速推理

做模型推理部署时,最让人头疼的往往不是模型效果,而是显存和算力。模型文件几个 GB 起步,单卡放不下就得分词、上多机,推理吞吐也上不去。因此这两年 INT8、INT4、FP8 等量化方案层出不穷。在这条路上,有一类方案看起来…

2026/10/23 5:08:58

老显卡UEFI黑屏怎么办?GOPUpd v1.9.6.5注入GOP驱动教程

简介:面向DIY装机与电脑维修人员的AMD/NVIDIA显卡GOP更新工具GOPUpd 1.9.6.5压缩包,用于升级显卡图形输出协议组件;GOP是连接图形硬件与UEFI固件的重要接口,老旧显卡或主板常因GOP版本过旧导致开机黑屏、无法识别新显卡&#xff0…

2026/10/22 11:29:28

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/23 0:02:26

Verilog实现FPGA密码锁:矩阵键盘与状态机设计实战

说实话,这几年接触了不少想入门FPGA的朋友,如果你问哪个项目能一口气把Verilog的核心基本功串起来,我通常会推荐电子密码锁。这个项目看着不起眼,却把矩阵键盘扫描、按键消抖、状态机设计、动态显示刷新、外设控制这些数字逻辑里的…

2026/10/23 0:02:26

数据服务监控运维实战:从指标体系到告警治理与故障排查

周末凌晨两点,手机震了。第一反应是“数据服务挂了”。打开电脑看了下,果然,某张核心报表的数据产出时间已经超过SLA两小时,下游三个业务方连着发来问询。这种场景,做过大数据运维的人都不会陌生。数据服务的监控与运维…

2026/10/23 0:02:26

FreeRTOS任务通知实战详解:内核原理、API与避坑指南

裸机编程切到RTOS之后,任务之间的通信和同步永远是躲不开的坎。前面聊过队列、信号量、互斥锁、事件组,这次把FreeRTOS任务通知(Task Notification)单独拎出来讲透。原因很简单:任务通知是FreeRTOS里最容易用错、也最容…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑