-
Notifications
You must be signed in to change notification settings - Fork 25
Open
Description
设计一个支持撤销(Undo)和重做(Redo)功能的编辑器是提高用户体验的重要部分。以下是一个基础的概念框架来实现这些功能,包括操作记录、状态管理和相关的实现策略。
1. 操作记录模型
- 操作类型:
- 每个操作可以被定义为一个对象,包含操作类型、目标状态和相关数据。
class Action {
constructor(type, data) {
this.type = type; // 操作类型,例如 "ADD", "REMOVE", "UPDATE"
this.data = data; // 操作相关的数据,例如变更的内容
}
}- 栈数据结构:
- 使用两个栈来存储用户的操作记录:
- Undo Stack: 存储需要撤销的操作。
- Redo Stack: 存储已撤销的操作,以便可以重做。
- 使用两个栈来存储用户的操作记录:
2. 基本操作
- 执行操作:
- 当用户进行某个操作时,将操作记录推入
undo stack,并清空redo stack。
- 当用户进行某个操作时,将操作记录推入
let undoStack = [];
let redoStack = [];
function performAction(action) {
// 执行操作
applyAction(action);
// 压入撤销栈
undoStack.push(action);
redoStack = []; // 清空重做栈
}- 撤销操作:
- 从
undo stack中弹出最后一个操作,并执行相应的撤销逻辑。将撤销的操作推入redo stack。
- 从
function undo() {
if (undoStack.length === 0) return;
const action = undoStack.pop();
reverseAction(action);
redoStack.push(action);
}
function reverseAction(action) {
switch (action.type) {
case 'ADD':
// 撤销添加:从当前状态中删除最后添加的元素
currentState.content.pop();
break;
case 'REMOVE':
// 撤销删除:将被删除的元素重新插入
const removedData = action.data; // 被删除的数据
currentState.content.splice(removedData.index, 0, removedData.value); // 插入被删除的元素
break;
case 'UPDATE':
// 撤销更新:恢复为旧值,需要在更新时保存旧值
const indexToRestore = action.data.index; // 取得要恢复的索引
currentState.content[indexToRestore] = action.data.oldValue; // 恢复为旧值
break;
}
}- 重做操作:
- 从
redo stack中弹出最后一个操作,并重复前面的执行逻辑,将其压入undo stack。
- 从
function redo() {
if (redoStack.length === 0) return;
const action = redoStack.pop();
performAction(action);
}3. 数据结构
- 状态管理:
- 定期保存编辑器的状态(例如,文本、对象、图形等)。每个操作都应该修改当前状态,并可以在需要时恢复到先前的状态。
let textContent = []; // 用于存储文本内容的数组
// 当前编辑状态
let currentState = {
content: textContent
};
function applyAction(action) {
switch (action.type) {
case 'ADD':
currentState.content.push(action.data); // 在数组末尾添加元素
break;
case 'REMOVE':
const indexToRemove = action.data.index; // 取得要删除的索引
if (indexToRemove > -1) {
currentState.content.splice(indexToRemove, 1); // 删除指定索引的元素
}
break;
case 'UPDATE':
const indexToUpdate = action.data.index; // 取得要更新的索引
if (indexToUpdate > -1) {
currentState.content[indexToUpdate] = action.data.newValue; // 更新指定索引的元素
}
break;
}
}4. 用户界面
- 界面反馈:
- 提供明确的用户界面元素(如按钮、菜单)来让用户进行撤销和重做操作,并在操作后更新按钮的可用性(即,当没有更多操作可以撤销或重做时,禁用相应的按钮)。
5. 复杂操作
- 组合操作:
- 如果用户在一个操作中进行了多个动作(例如,选择多个文本并更改样式),可以将这些操作封装为一个复合操作,进行单次撤销和重做。
完整示例
let undoStack = []
let redoStack = []
let textContent = []
interface IAction {
type: 'ADD' | 'REMOVE' | 'UPDATE'
data: {
index?: number
newValue?: string
oldValue?: string
} | string
}
// 执行操作
function performAction(action: IAction) {
applyAction(action)
undoStack.push(action)
redoStack = [] // 当执行新操作时,清空重做堆栈
}
function applyAction(action : IAction) {
switch (action.type) {
case 'ADD':
// 处理新增操作
textContent.push(action.data)
break;
case 'REMOVE':
// 处理删除操作
const indexToRemove = action.data.index
if (indexToRemove > -1) {
action.data.oldValue = textContent[indexToRemove]
textContent.splice(indexToRemove, 1)
}
break;
case 'UPDATE':
const indexToUpdate = action.data.index
if (indexToUpdate > -1) {
action.data.oldValue = textContent[indexToUpdate] // 保存旧值以便撤销
textContent[indexToUpdate] = action.data.newValue
}
break;
}
}
// 撤销操作
function reverseAction(action: IAction) {
switch (action.type) {
case 'ADD':
// 撤销新增操作
textContent.pop()
break;
case 'REMOVE':
// 撤销删除操作
const removeData = action.data
textContent.splice(removeData.index, 0, removeData.oldValue)
break;
case 'UPDATE':
const indexToUpdate = action.data.index
if (indexToUpdate > -1) {
textContent[indexToUpdate] = action.data.oldValue
}
break;
}
}
// 撤销操作
function undo() {
if (undoStack.length === 0) return
const action = undoStack.pop()
reverseAction(action)
redoStack.push(action)
}
// 重做操作
function redo() {
if (redoStack.length === 0) return
const action = redoStack.pop()
performAction(action)
}
// 示例操作
performAction({ type: 'ADD', data: 'Hello' });
performAction({ type: 'ADD', data: 'World' });
performAction({ type: 'UPDATE', data: { index: 1, newValue: 'Everyone' } });
performAction({ type: 'REMOVE', data: { index: 0 } });
console.log(textContent); // ["Everyone"]
undo(); // 撤销删除
console.log(textContent); // ["Hello", "Everyone"]
redo(); // 重做删除
console.log(textContent); // ["Everyone"]小结
通过维护撤销和重做的栈数据结构、定义操作模型、记录当前状态和提供 UI 反馈,可以有效地实现编辑器中的撤销/重做功能。将这些机制结合在一起,可以创建一个用户友好的编辑体验,方便用户管理和恢复他们的操作。
Metadata
Metadata
Assignees
Labels
No labels