我正在考虑在有角度的应用程序中实现撤消,重做功能的方法。
第一个也是最基本的想法是使用一个完全描述应用程序内部的模型,称之为AppModel
。对于每个值得记录的更改,您只需创建一个新对象AppModel
并将其推入堆栈并更新currentIndex。
在绝对最坏的情况下,AppModel的对象必须填写500个文本字段,平均长度为20个字符。每次更新就是10000个字符或10kB。
这个数字有多糟?我不认为这是否会导致内存问题,但是否会使每次推送堆栈时应用程序冻结?这是一个基本的实现:
historyStack: AppModel[];
currentIndex:number = -1;
push(newAppModel:AppModel){
//delete all after current index
this.historyStack.splice(++this.currentIndex, 0, newAppModel);
}
forward(){
if(this.currentIndex < this.historyStack.length-1){
this.currentIndex++;
return this.historyStack[this.currentIndex];
}
return this.historyStack[this.currentIndex];
}
back(){
return this.historyStack[this.currentIndex--];
}
Run Code Online (Sandbox Code Playgroud)
我想到的另一种选择是存储执行redo
和reverse
操作的函数调用。这种方法将需要我还存储哪些对象需要调用函数。用户可能会删除这些对象,因此还必须有一种重新创建对象的方法。当我输入时,这变得很痛苦:)
您推荐什么方式?
这就是为什么建议不要将状态放在单个对象中,而是使用(业务)模块,其中每个模块都具有合理数量的属性。
我建议使用 NGRX 或 NGXS 等 Redux 框架进行状态管理。对于 NGRX,有一个元减速器库https://www.npmjs.com/package/ngrx-wieder可以像这样包装您的 NGRX 减速器:
const reducer = (state, action: Actions, listener?: PatchListener) =>
produce(state, next => {
switch (action.type) {
case addTodo.type:
next.todos.push({id: id(), text: action.text, checked: false})
return
case toggleTodo.type:
const todo = next.todos.find(t => t.id === action.id)
todo.checked = !todo.checked
return
case removeTodo.type:
next.todos.splice(next.todos.findIndex(t => t.id === action.id), 1)
return
case changeMood.type:
next.mood = action.mood
return
default:
return
}
}, listener)
const undoableReducer = undoRedo({
track: true,
mergeActionTypes: [
changeMood.type
]
})(reducer)
export function appReducer(state = App.initial, action: Actions) {
return undoableReducer(state, action)
}
Run Code Online (Sandbox Code Playgroud)
这样你就不必一遍又一遍地为每个模块的减速器编写撤消/重做逻辑,只需将其包装在元减速器中即可。并且您可以排除状态中不需要撤消的重部分。您可以在这里找到完整的 Stackblitz 示例,以及实现代码的基本部分(利用 ImmerJS 进行修补):https ://nils-mehlhorn.de/posts/angular-undo-redo-ngrx-redux