react实现撤销恢复
实现撤销/恢复功能的核心思路
在React中实现撤销和恢复功能通常需要维护一个状态历史记录,并跟踪当前状态的索引。以下是几种常见方法:
方法一:使用useReducer + 历史记录管理
通过useReducer管理状态,同时保存状态变化的历史记录:

function useUndoReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
const [history, setHistory] = useState([initialState]);
const [index, setIndex] = useState(0);
function undo() {
if (index > 0) {
setIndex(index - 1);
setState(history[index - 1]);
}
}
function redo() {
if (index < history.length - 1) {
setIndex(index + 1);
setState(history[index + 1]);
}
}
function dispatch(action) {
const newState = reducer(state, action);
setHistory([...history.slice(0, index + 1), newState]);
setIndex(index + 1);
setState(newState);
}
return [state, dispatch, { undo, redo, canUndo: index > 0, canRedo: index < history.length - 1 }];
}
方法二:使用第三方库
Immer和Zustand等库提供了内置的撤销/恢复支持:
import { produceWithPatches, applyPatches } from 'immer';
function useUndoableState(initialState) {
const [state, setState] = useState(initialState);
const [history, setHistory] = useState([]);
const [future, setFuture] = useState([]);
const updateState = (newState) => {
const [nextState, patches, inversePatches] = produceWithPatches(state, draft => {
Object.assign(draft, newState);
});
setState(nextState);
setHistory([...history, inversePatches]);
setFuture([]);
};
const undo = () => {
if (history.length > 0) {
const lastPatches = history[history.length - 1];
const [prevState] = applyPatches(state, lastPatches);
setState(prevState);
setHistory(history.slice(0, -1));
setFuture([lastPatches, ...future]);
}
};
const redo = () => {
if (future.length > 0) {
const [nextPatches] = future;
const [nextState] = applyPatches(state, nextPatches);
setState(nextState);
setHistory([...history, nextPatches]);
setFuture(future.slice(1));
}
};
return [state, updateState, { undo, redo }];
}
方法三:基于命令模式实现
对于复杂应用,可以采用命令模式封装每个操作:

class Command {
constructor(execute, undo, value) {
this.execute = execute;
this.undo = undo;
this.value = value;
}
}
function useCommandStack() {
const [stack, setStack] = useState([]);
const [pointer, setPointer] = useState(-1);
const execute = (command) => {
command.execute();
setStack([...stack.slice(0, pointer + 1), command]);
setPointer(pointer + 1);
};
const undo = () => {
if (pointer >= 0) {
stack[pointer].undo();
setPointer(pointer - 1);
}
};
const redo = () => {
if (pointer < stack.length - 1) {
stack[pointer + 1].execute();
setPointer(pointer + 1);
}
};
return { execute, undo, redo };
}
性能优化建议
对于大型状态对象,考虑以下优化策略:
- 使用差异记录而非完整状态快照
- 限制历史记录的最大长度
- 实现节流机制防止高频操作产生过多历史记录
- 对于不可变数据使用结构共享
实际应用示例
以下是表单编辑场景的撤销/恢复实现:
function FormEditor() {
const [formData, setFormData, { undo, redo, canUndo, canRedo }] = useUndoableState({
name: '',
email: '',
age: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
return (
<div>
<input name="name" value={formData.name} onChange={handleChange} />
<input name="email" value={formData.email} onChange={handleChange} />
<input name="age" value={formData.age} onChange={handleChange} />
<button onClick={undo} disabled={!canUndo}>Undo</button>
<button onClick={redo} disabled={!canRedo}>Redo</button>
</div>
);
}






