Undo/Redo 在 Vuex 中使用状态快照
Undo/Redo in Vuex using state snapshot
上下文
我构建了一个可用于以下步骤的撤消-重做插件。
使用的步骤(概述)
- 在每个动作上添加之前的状态快照。
- 使用以 previousState 作为参数的自定义 replaceState 方法撤消。
- 使用以 previousState 作为参数的自定义 replaceState 方法重做。
目前正在处理性能问题,因为我们要处理巨大的嵌套状态。
性能问题
我已经使用 cloneDeep 对状态进行深度复制,因此在替换
状态,随着状态变得庞大,cloneDeep 需要更多的时间来
克隆。
我使用了一个自定义的 replaceState 方法,它做的事情与 replaceState 完全相同,但是替换了模块状态而不是整个应用程序的状态,这最终也需要更长的时间才能
替换。
由于上述问题,undo/redo 变得迟缓且无法使用。
我有点卡住了。我哪里出错了,或者是否有更好的方法来实现复杂状态应用程序 undo/redo?
代码
done:用于撤消和存储状态快照。
undone:用于重做和存储状态快照。
GlobalStore.js:所有模块组合在一起的全局商店。
export const store = new Vuex.Store({
modules: { canvasEditor,dashboardMetrics }
plugins: [ canvasEditorUndoRedo ]
})
以下是插件中的文件。
index.js
import { Operations } from ‘./Operations’;
import { actionsToBeUsed } from './constants';
import { cloneDeep } from 'lodash';
export const op = new Operations();
export const canvasEditorUndoRedo = (store) => {
/*
Description: Perform undo/redo operation
Steps:
1.Initalize the class
2.Store the previous state based on actions
*/
op.init(store);
store.subscribeAction((action, state) => {
if (action.type != 'undo' &&action.type != 'redo' && actionsToBeUsed.find(actionType => actionType == action.type) != undefined) {
// Store the state
let stateClone = cloneDeep(state.canvasEditor);
op.addSnapshot({ id: op.done.length + 1, action, state: stateClone });
}
});
}
Operation.js
import { cloneDeep } from 'lodash';
export class Operations {
store
done=[]
undone = []
init(store) {
this.store = store;
}
addSnapshot(snapshot) {
this.done.push(snapshot);
this.updateOperationsCount();
}
clearUndo() {
this.done = [];
this.updateOperationsCount();
}
clearRedo() {
this.undone = [];
this.updateOperationsCount();
}
undo() {
/*
Description: Performs undo operation based on previousState stored in array(done).
Steps:
I. Get the last stored action from array(done) pop it out.
II. Push the undo element(popped element) to redo’s an array (undone).
III. Replace the current state with stored state.
*/
if (this.done.length != 0) {
//I
let undoELement = this.done.pop();
//II
this.undone.push({ id: undoELement.id, action: undoELement.action, state: this.store.state.canvasEditor});
//III
let state = cloneDeep(undoELement.state);
this.replaceCanvasEditorState(state);
this.updateOperationsCount();
}
redo() {
/*
Description: Performs redo operation based on State stored in array(undone).
Steps:
I. Get (pop) the last undo element from undone
II. Push the undo element(popped element) to undo’s an array (done) .
III. Replace the current state with stored state.
*/
if (this.undone.length != 0) {
//I
let redoELement = this.undone.pop();
//II
this.done.push({ id: redoELement.id, action: redoELement.action, state: this.store.state.canvasEditor });
//III
let state = cloneDeep(redoELement.state);
this.replaceCanvasEditorState(state);
this.updateOperationsCount();
}
}
replaceCanvasEditorState(state) {
/*
Description: Replaces current state with state provided as parameter.
*/
this.store._withCommit(() => {
this.store.state.canvasEditor = state;
});
}
updateOperationsCount() {
let undoRedo = {
doneLength: this.done.length,
undoneLength: this.undone.length
}
this.store.commit('updateUndoRedoCount', undoRedo);
}
}
CodeSandBox
该应用程序处理很多复杂的操作。
恐怕使用快照(深度克隆)在巨大的嵌套状态上实现 undo/redo 总是很慢,更不用说内存占用了
所以你最好的选择可能是完全改变你的策略。当您使用 Vuex 时,每个更改都必须使用突变来完成,因此使用 action/reverse 操作策略来实现它应该不会太难。在每个突变中,不是将状态快照推送到堆栈上,而是将“反向操作”突变推送到堆栈上(使用旧的 sub-state 作为参数 - deepClone
d 如果它是一个对象)。
当然它不像您原来的方法那样透明,并且需要突变作者以特定方式编写突变,但您总是可以编写一些帮助程序以使其更容易和“标准化”...
或者您可以查看 undo-redo-vuex Vuex 插件,它通过保存初始状态和执行的所有变更来实现 undo/redo。当需要撤消时,它将状态重置为初始状态并重播除最后一个之外的所有突变。不知道这种扩展有多好,但至少它对突变作者来说更透明...
上下文
我构建了一个可用于以下步骤的撤消-重做插件。
使用的步骤(概述)
- 在每个动作上添加之前的状态快照。
- 使用以 previousState 作为参数的自定义 replaceState 方法撤消。
- 使用以 previousState 作为参数的自定义 replaceState 方法重做。
目前正在处理性能问题,因为我们要处理巨大的嵌套状态。
性能问题
我已经使用 cloneDeep 对状态进行深度复制,因此在替换 状态,随着状态变得庞大,cloneDeep 需要更多的时间来 克隆。
我使用了一个自定义的 replaceState 方法,它做的事情与 replaceState 完全相同,但是替换了模块状态而不是整个应用程序的状态,这最终也需要更长的时间才能
替换。
由于上述问题,undo/redo 变得迟缓且无法使用。
我有点卡住了。我哪里出错了,或者是否有更好的方法来实现复杂状态应用程序 undo/redo?
代码
done:用于撤消和存储状态快照。
undone:用于重做和存储状态快照。
GlobalStore.js:所有模块组合在一起的全局商店。
export const store = new Vuex.Store({
modules: { canvasEditor,dashboardMetrics }
plugins: [ canvasEditorUndoRedo ]
})
以下是插件中的文件。
index.js
import { Operations } from ‘./Operations’;
import { actionsToBeUsed } from './constants';
import { cloneDeep } from 'lodash';
export const op = new Operations();
export const canvasEditorUndoRedo = (store) => {
/*
Description: Perform undo/redo operation
Steps:
1.Initalize the class
2.Store the previous state based on actions
*/
op.init(store);
store.subscribeAction((action, state) => {
if (action.type != 'undo' &&action.type != 'redo' && actionsToBeUsed.find(actionType => actionType == action.type) != undefined) {
// Store the state
let stateClone = cloneDeep(state.canvasEditor);
op.addSnapshot({ id: op.done.length + 1, action, state: stateClone });
}
});
}
Operation.js
import { cloneDeep } from 'lodash';
export class Operations {
store
done=[]
undone = []
init(store) {
this.store = store;
}
addSnapshot(snapshot) {
this.done.push(snapshot);
this.updateOperationsCount();
}
clearUndo() {
this.done = [];
this.updateOperationsCount();
}
clearRedo() {
this.undone = [];
this.updateOperationsCount();
}
undo() {
/*
Description: Performs undo operation based on previousState stored in array(done).
Steps:
I. Get the last stored action from array(done) pop it out.
II. Push the undo element(popped element) to redo’s an array (undone).
III. Replace the current state with stored state.
*/
if (this.done.length != 0) {
//I
let undoELement = this.done.pop();
//II
this.undone.push({ id: undoELement.id, action: undoELement.action, state: this.store.state.canvasEditor});
//III
let state = cloneDeep(undoELement.state);
this.replaceCanvasEditorState(state);
this.updateOperationsCount();
}
redo() {
/*
Description: Performs redo operation based on State stored in array(undone).
Steps:
I. Get (pop) the last undo element from undone
II. Push the undo element(popped element) to undo’s an array (done) .
III. Replace the current state with stored state.
*/
if (this.undone.length != 0) {
//I
let redoELement = this.undone.pop();
//II
this.done.push({ id: redoELement.id, action: redoELement.action, state: this.store.state.canvasEditor });
//III
let state = cloneDeep(redoELement.state);
this.replaceCanvasEditorState(state);
this.updateOperationsCount();
}
}
replaceCanvasEditorState(state) {
/*
Description: Replaces current state with state provided as parameter.
*/
this.store._withCommit(() => {
this.store.state.canvasEditor = state;
});
}
updateOperationsCount() {
let undoRedo = {
doneLength: this.done.length,
undoneLength: this.undone.length
}
this.store.commit('updateUndoRedoCount', undoRedo);
}
}
CodeSandBox
该应用程序处理很多复杂的操作。
恐怕使用快照(深度克隆)在巨大的嵌套状态上实现 undo/redo 总是很慢,更不用说内存占用了
所以你最好的选择可能是完全改变你的策略。当您使用 Vuex 时,每个更改都必须使用突变来完成,因此使用 action/reverse 操作策略来实现它应该不会太难。在每个突变中,不是将状态快照推送到堆栈上,而是将“反向操作”突变推送到堆栈上(使用旧的 sub-state 作为参数 - deepClone
d 如果它是一个对象)。
当然它不像您原来的方法那样透明,并且需要突变作者以特定方式编写突变,但您总是可以编写一些帮助程序以使其更容易和“标准化”...
或者您可以查看 undo-redo-vuex Vuex 插件,它通过保存初始状态和执行的所有变更来实现 undo/redo。当需要撤消时,它将状态重置为初始状态并重播除最后一个之外的所有突变。不知道这种扩展有多好,但至少它对突变作者来说更透明...