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