前端备忘录模式fe-interview:状态保存恢复
痛点:为什么你的应用需要状态回溯能力?
在日常开发中,你是否遇到过这样的场景:
- 用户误操作后无法恢复到之前的状态
- 复杂的表单填写过程中需要临时保存进度
- 需要实现撤销/重做功能但不知从何入手
- 应用崩溃后用户数据全部丢失
这些问题的核心在于状态管理的缺失。备忘录模式(Memento Pattern)正是解决这类问题的利器,它允许在不破坏封装性的前提下捕获并外部化对象的内部状态,以便后续可以恢复到此状态。
备忘录模式核心概念
三大角色解析
模式对比表
| 设计模式 | 主要目的 | 适用场景 | 前端应用 |
|---|---|---|---|
| 备忘录模式 | 状态保存与恢复 | 撤销/重做、历史记录 | 表单状态管理、编辑器 |
| 观察者模式 | 状态变化通知 | 事件处理、数据绑定 | Vue/React状态管理 |
| 策略模式 | 算法替换 | 条件分支处理 | 表单验证策略 |
前端实战:实现可撤销的表单组件
基础实现方案
// 备忘录类 - 负责存储状态
class FormMemento {
constructor(state) {
this.state = JSON.parse(JSON.stringify(state)); // 深拷贝
this.timestamp = new Date();
}
getState() {
return this.state;
}
}
// 原发器 - 表单状态管理者
class FormOriginator {
constructor() {
this.state = {
username: '',
email: '',
phone: '',
address: ''
};
}
// 创建备忘录
createMemento() {
return new FormMemento(this.state);
}
// 从备忘录恢复状态
restore(memento) {
this.state = memento.getState();
}
// 更新状态
updateField(field, value) {
this.state[field] = value;
}
getState() {
return this.state;
}
}
// 负责人 - 管理备忘录历史
class FormCaretaker {
constructor() {
this.history = [];
this.currentIndex = -1;
}
// 保存状态
saveState(originator) {
// 清除当前索引之后的历史
if (this.currentIndex < this.history.length - 1) {
this.history = this.history.slice(0, this.currentIndex + 1);
}
const memento = originator.createMemento();
this.history.push(memento);
this.currentIndex = this.history.length - 1;
}
// 撤销
undo(originator) {
if (this.currentIndex > 0) {
this.currentIndex--;
originator.restore(this.history[this.currentIndex]);
return true;
}
return false;
}
// 重做
redo(originator) {
if (this.currentIndex < this.history.length - 1) {
this.currentIndex++;
originator.restore(this.history[this.currentIndex]);
return true;
}
return false;
}
// 获取历史记录
getHistory() {
return this.history.map((memento, index) => ({
index,
timestamp: memento.timestamp,
isCurrent: index === this.currentIndex
}));
}
}
React集成示例
import React, { useReducer, useCallback } from 'react';
// 使用useReducer管理复杂状态
const formReducer = (state, action) => {
switch (action.type) {
case 'UPDATE_FIELD':
return {
...state,
[action.field]: action.value
};
case 'RESTORE_STATE':
return action.state;
default:
return state;
}
};
const useFormWithHistory = (initialState) => {
const [state, dispatch] = useReducer(formReducer, initialState);
const [history, setHistory] = React.useState([]);
const [currentIndex, setCurrentIndex] = React.useState(-1);
// 保存当前状态到历史记录
const saveState = useCallback(() => {
const newMemento = JSON.parse(JSON.stringify(state));
setHistory(prev => {
// 清除重做历史
const newHistory = prev.slice(0, currentIndex + 1);
newHistory.push(newMemento);
setCurrentIndex(newHistory.length - 1);
return newHistory;
});
}, [state, currentIndex]);
// 更新字段并自动保存
const updateField = useCallback((field, value) => {
dispatch({ type: 'UPDATE_FIELD', field, value });
saveState();
}, [saveState]);
// 撤销
const undo = useCallback(() => {
if (currentIndex > 0) {
const newIndex = currentIndex - 1;
setCurrentIndex(newIndex);
dispatch({ type: 'RESTORE_STATE', state: history[newIndex] });
}
}, [currentIndex, history]);
// 重做
const redo = useCallback(() => {
if (currentIndex < history.length - 1) {
const newIndex = currentIndex + 1;
setCurrentIndex(newIndex);
dispatch({ type: 'RESTORE_STATE', state: history[newIndex] });
}
}, [currentIndex, history]);
return {
state,
updateField,
undo,
redo,
canUndo: currentIndex > 0,
canRedo: currentIndex < history.length - 1,
history
};
};
// 使用示例
const UserForm = () => {
const { state, updateField, undo, redo, canUndo, canRedo } = useFormWithHistory({
username: '',
email: '',
phone: ''
});
return (
<div>
<input
value={state.username}
onChange={(e) => updateField('username', e.target.value)}
placeholder="用户名"
/>
<input
value={state.email}
onChange={(e) => updateField('email', e.target.value)}
placeholder="邮箱"
/>
<input
value={state.phone}
onChange={(e) => updateField('phone', e.target.value)}
placeholder="手机号"
/>
<div>
<button onClick={undo} disabled={!canUndo}>
撤销
</button>
<button onClick={redo} disabled={!canRedo}>
重做
</button>
</div>
</div>
);
};
性能优化策略
状态快照优化
class OptimizedMementoManager {
constructor(maxHistorySize = 50, throttleDelay = 500) {
this.maxHistorySize = maxHistorySize;
this.throttleDelay = throttleDelay;
this.history = [];
this.currentIndex = -1;
this.lastSaveTime = 0;
}
// 节流保存
throttledSave(originator, force = false) {
const now = Date.now();
if (force || now - this.lastSaveTime > this.throttleDelay) {
this.save(originator);
this.lastSaveTime = now;
}
}
// 差异保存(只保存变化的部分)
differentialSave(originator, prevState) {
const currentState = originator.getState();
const changes = this.getStateChanges(prevState, currentState);
if (Object.keys(changes).length > 0) {
this.save({
...prevState,
...changes,
__timestamp: Date.now()
});
}
}
getStateChanges(oldState, newState) {
const changes = {};
for (const key in newState) {
if (oldState[key] !== newState[key]) {
changes[key] = newState[key];
}
}
return changes;
}
// 清理过期历史
cleanupHistory() {
if (this.history.length > this.maxHistorySize) {
this.history = this.history.slice(-this.maxHistorySize);
this.currentIndex = Math.min(this.currentIndex, this.maxHistorySize - 1);
}
}
}
内存管理最佳实践
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 完整快照 | 实现简单,恢复准确 | 内存占用大 | 小型状态对象 |
| 差异保存 | 内存效率高 | 实现复杂,需要比较算法 | 大型状态对象 |
| 懒保存 | 减少不必要的保存 | 可能丢失中间状态 | 高频状态变化 |
实际应用场景分析
场景一:富文本编辑器
场景二:图形绘制应用
class DrawingAppMemento {
constructor(canvasState, drawingHistory, selectionState) {
this.canvasState = canvasState;
this.drawingHistory = [...drawingHistory]; // 绘制操作历史
this.selectionState = selectionState;
this.timestamp = Date.now();
}
}
// 针对图形应用的优化备忘录
class DrawingMementoManager {
constructor() {
this.keyframes = new Map(); // 关键帧存储
this.operations = []; // 操作记录
}
// 保存关键帧(完整状态)
saveKeyframe(state) {
const keyframe = new DrawingAppMemento(
JSON.parse(JSON.stringify(state.canvas)),
[...state.history],
JSON.parse(JSON.stringify(state.selection))
);
this.keyframes.set(Date.now(), keyframe);
}
// 保存操作(增量变化)
saveOperation(operation) {
this.operations.push({
...operation,
timestamp: Date.now()
});
}
// 恢复到某个时间点
restoreToTimestamp(timestamp) {
// 找到最近的关键帧
const keyframeTime = this.findNearestKeyframe(timestamp);
const keyframe = this.keyframes.get(keyframeTime);
// 重放关键帧之后的操作直到目标时间
const operationsToReplay = this.operations.filter(op =>
op.timestamp > keyframeTime && op.timestamp <= timestamp
);
return { keyframe, operationsToReplay };
}
}
高级模式:分布式状态管理
与Redux集成
// 基于Redux的备忘录中间件
const mementoMiddleware = store => next => action => {
const result = next(action);
if (action.meta?.saveHistory) {
const currentState = store.getState();
const memento = {
state: currentState,
action: action.type,
timestamp: Date.now()
};
// 保存到历史
store.dispatch({
type: 'HISTORY_ADD',
payload: memento
});
}
return result;
};
// Reducer处理历史状态
const historyReducer = (state = { history: [], currentIndex: -1 }, action) => {
switch (action.type) {
case 'HISTORY_ADD':
const newHistory = state.history.slice(0, state.currentIndex + 1);
newHistory.push(action.payload);
return {
history: newHistory,
currentIndex: newHistory.length - 1
};
case 'HISTORY_UNDO':
if (state.currentIndex > 0) {
return {
...state,
currentIndex: state.currentIndex - 1
};
}
return state;
case 'HISTORY_REDO':
if (state.currentIndex < state.history.length - 1) {
return {
...state,
currentIndex: state.currentIndex + 1
};
}
return state;
default:
return state;
}
};
测试策略与质量保证
单元测试示例
describe('Memento Pattern', () => {
let originator;
let caretaker;
beforeEach(() => {
originator = new FormOriginator();
caretaker = new FormCaretaker();
});
test('should save and restore state correctly', () => {
// 初始状态
originator.updateField('username', 'testuser');
caretaker.saveState(originator);
// 修改状态
originator.updateField('username', 'modifieduser');
// 恢复状态
caretaker.undo(originator);
expect(originator.getState().username).toBe('testuser');
});
test('should handle multiple undo/redo operations', () => {
const testValues = ['state1', 'state2', 'state3'];
testValues.forEach((value, index) => {
originator.updateField('testField', value);
caretaker.saveState(originator);
});
// 撤销两次
caretaker.undo(originator);
caretaker.undo(originator);
expect(originator.getState().testField).toBe('state1');
// 重做一次
caretaker.redo(originator);
expect(originator.getState().testField).toBe('state2');
});
test('should clear redo history when new action is performed', () => {
// 创建一些历史记录
['a', 'b', 'c'].forEach(val => {
originator.updateField('field', val);
caretaker.saveState(originator);
});
// 撤销到第一个状态
caretaker.undo(originator);
caretaker.undo(originator);
// 执行新操作应该清除重做历史
originator.updateField('field', 'new');
caretaker.saveState(originator);
expect(caretaker.getHistory().length).toBe(2); // 只有初始状态和新状态
expect(caretaker.redo(originator)).toBe(false); // 不能重做
});
});
总结与最佳实践
备忘录模式在前端开发中提供了强大的状态管理能力,特别是在需要实现撤销/重做、状态历史、临时保存等功能的场景中。通过合理的实现和优化,可以在不显著影响性能的前提下为用户提供流畅的状态管理体验。
关键收获
- 封装性保持:备忘录模式允许在不暴露对象内部细节的情况下保存和恢复状态
- 历史管理:通过负责人(Caretaker)可以轻松管理多个状态快照
- 用户体验提升:为用户提供撤销、重做、历史查看等功能
- 数据安全:防止意外操作导致的数据丢失
实施建议
- 对于小型状态对象,使用完整快照方式
- 对于大型状态,考虑差异保存或懒加载策略
- 合理设置历史记录大小限制,避免内存溢出
- 在React/Vue等框架中,结合hooks/composition API实现更优雅
备忘录模式不仅是面试中的重要考点,更是实际开发中提升应用质量和用户体验的实用工具。掌握这一模式,让你在前端状态管理的道路上更加游刃有余。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



