@knotx/plugins-history
v0.5.10
Published
History Plugin for Knotx
Readme
@knotx/plugins-history
历史记录插件,为 KnotX 提供撤销/重做功能。
安装
npm install @knotx/plugins-history概述
History 插件为 KnotX 提供了完整的历史记录管理功能,包括撤销、重做、标记状态等。该插件能够跟踪所有数据变化,并提供便捷的历史记录操作接口。
实现原理
History 插件的核心实现原理:
- 操作拦截:拦截所有数据管理器的操作,记录变化前后的状态
- 缓冲机制:使用防抖机制批量处理操作,避免频繁的历史记录创建
- 反向操作:为每个操作生成反向操作,实现撤销功能
- 状态标记:支持标记特定状态,快速回到指定的历史点
依赖关系
核心依赖
@knotx/core:提供基础插件架构和数据管理@knotx/decorators:提供装饰器支持rxjs:提供响应式编程支持
自动依赖
@knotx/core中的nodesManager和edgesManager:自动注册到历史记录系统
API 文档
主要类
History
历史记录插件的主要类,继承自 BasePlugin。
export class History<T extends Record<string, IData> = Record<string, any>> extends BasePlugin<'history', HistoryConfig> {
name = 'history' as const
}配置选项
HistoryConfig
export interface HistoryConfig {
/** 最大历史记录数量 */
maxHistory?: number
/** 防抖时间(毫秒) */
debounceTime?: number
}插件数据 (PluginData)
History 插件提供以下数据:
interface PluginData {
history: {
ref: IHistory
canUndo: boolean
canRedo: boolean
registerDataManager: (dataManager: DataManager<any>) => (() => void)
}
}接口定义
IHistory
export interface IHistory {
isUndoRedo: boolean
undo: () => void
redo: () => void
addTag: (tag: string) => void
removeTag: (tag: string) => void
toTag: (tag: string) => void
clearTags: () => void
}HistoryState
export interface HistoryState<T extends Record<string, IData> = Record<string, any>> {
value: {
[key: string]: HistoryOperation<T>[]
}
timestamp: number
}使用示例
基本用法
import { History } from '@knotx/plugins-history'
const engine = new Engine({
plugins: [History],
pluginConfig: {
history: {
maxHistory: 50,
debounceTime: 100,
},
},
})撤销和重做操作
const engine = new Engine({
plugins: [History],
})
const historyData = engine.getPluginData('history')
// 撤销操作
if (historyData.canUndo) {
historyData.ref.undo()
}
// 重做操作
if (historyData.canRedo) {
historyData.ref.redo()
}监听历史记录状态
class HistoryUIPlugin extends BasePlugin {
@inject.history.canUndo()
canUndo!: boolean
@inject.history.canRedo()
canRedo!: boolean
@inject.history.ref()
history!: IHistory
@panel('bottom')
render() {
return (
<div>
<button
disabled={!this.canUndo}
onClick={() => this.history.undo()}
>
撤销
</button>
<button
disabled={!this.canRedo}
onClick={() => this.history.redo()}
>
重做
</button>
</div>
)
}
}状态标记功能
const engine = new Engine({
plugins: [History],
})
const historyData = engine.getPluginData('history')
// 标记当前状态
historyData.ref.addTag('checkpoint-1')
// 在进行一系列操作后...
// 回到标记的状态
historyData.ref.toTag('checkpoint-1')
// 移除标记
historyData.ref.removeTag('checkpoint-1')
// 清空所有标记
historyData.ref.clearTags()自定义数据管理器集成
class CustomDataManager extends DataManager<MyData> {
constructor() {
super('custom')
}
}
const engine = new Engine({
plugins: [History],
})
const historyData = engine.getPluginData('history')
const customManager = new CustomDataManager()
// 注册自定义数据管理器
const unregister = historyData.registerDataManager(customManager)
// 取消注册
unregister()高级功能
批量操作优化
class BatchHistoryPlugin extends BasePlugin {
@inject.history.ref()
history!: IHistory
performBatchOperation() {
// 批量操作会自动被防抖处理
engine.dispatchNodeOperation({
type: 'add',
data: { id: 'node1', /* ... */ },
})
engine.dispatchNodeOperation({
type: 'add',
data: { id: 'node2', /* ... */ },
})
// 这些操作会被合并为一个历史记录条目
}
}条件性历史记录
class ConditionalHistoryPlugin extends BasePlugin {
@inject.history.ref()
history!: IHistory
@inject.nodesManager()
nodesManager!: DataManager<Node>
performTemporaryOperation() {
// 检查是否在撤销/重做过程中
if (this.history.isUndoRedo) {
// 在撤销/重做过程中,避免创建新的历史记录
return
}
// 正常操作
this.nodesManager.dispatch({
type: 'update',
data: { id: 'node1', /* ... */ },
})
}
}历史记录状态监听
class HistoryMonitorPlugin extends BasePlugin {
@inject.history.ref()
history!: IHistory
@subscribe.history.canUndo()
onCanUndoChange(canUndo: boolean) {
console.log('Can undo:', canUndo)
}
@subscribe.history.canRedo()
onCanRedoChange(canRedo: boolean) {
console.log('Can redo:', canRedo)
}
// 自定义历史记录监听
@OnInit
init() {
// 监听历史记录变化
this.history.onHistoryChange?.subscribe((state) => {
console.log('History changed:', state)
})
}
}性能优化
防抖配置
const engine = new Engine({
plugins: [History],
pluginConfig: {
history: {
debounceTime: 200, // 增加防抖时间以减少历史记录条目
},
},
})历史记录限制
const engine = new Engine({
plugins: [History],
pluginConfig: {
history: {
maxHistory: 20, // 减少最大历史记录数量以节省内存
},
},
})文件目录结构
packages/plugins-history/
├── src/
│ ├── history.ts # 主要实现文件
│ └── index.ts # 导出文件
├── dist/ # 构建输出目录
├── package.json # 包配置文件
├── build.config.ts # 构建配置
├── tsconfig.json # TypeScript 配置
├── eslint.config.mjs # ESLint 配置
└── CHANGELOG.md # 更新日志核心文件说明
- history.ts:包含 History 插件的主要实现,包括操作拦截、历史记录管理和状态标记功能
- index.ts:导出 History 类和相关类型定义
最佳实践
内存管理
- 合理设置历史记录数量:根据应用需求设置合适的
maxHistory值 - 及时清理标记:不再需要的状态标记应及时清理
- 避免过度防抖:过长的防抖时间可能影响用户体验
用户体验
- 提供撤销/重做按钮:在 UI 中提供清晰的撤销/重做操作入口
- 显示操作状态:根据
canUndo和canRedo状态禁用/启用按钮 - 键盘快捷键:支持
Ctrl+Z和Ctrl+Y快捷键
开发调试
- 监听历史记录状态:在开发阶段监听历史记录变化以便调试
- 使用状态标记:在关键操作点设置状态标记,方便测试和调试
注意事项
- 大量数据性能:历史记录会占用内存,大量数据时注意性能优化
- 异步操作:异步操作可能导致历史记录顺序混乱,需要特别注意
- 数据一致性:确保撤销/重做操作不会破坏数据的一致性
- 循环引用:避免在历史记录处理过程中创建循环引用
许可证
MIT
