侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

使用 Proxy 实现支持撤销/重做的不可变状态管理器

2025-12-12 / 0 评论 / 4 阅读

题目

使用 Proxy 实现支持撤销/重做的不可变状态管理器

信息

  • 类型:问答
  • 难度:⭐⭐⭐

考点

Proxy 高级应用, 不可变数据模式, 命令模式, 状态管理, 内存优化

快速回答

实现要点:

  • 使用 Proxy 拦截 set/deleteProperty 操作
  • 采用命令模式记录操作历史(undoStack/redoStack)
  • 结合 Reflect 执行默认操作
  • 使用深拷贝或结构共享保证不可变性
  • 实现撤销/重做时的状态回滚机制
## 解析

核心原理

通过 Proxy 创建状态对象的拦截层,在修改操作时:

  1. 捕获变更操作(set/deleteProperty)
  2. 记录当前状态快照和逆操作指令
  3. 使用不可变方式更新状态
  4. 管理两个栈(undoStack/redoStack)实现历史追踪

代码实现

class StateManager {
  constructor(initialState) {
    this.undoStack = [];
    this.redoStack = [];
    this._state = initialState;

    this.state = new Proxy(initialState, {
      set: (target, prop, value, receiver) => {
        const prevValue = target[prop];
        const success = Reflect.set(target, prop, value, receiver);

        if (success && !this.isUndoingRedoing) {
          // 记录操作历史
          this.undoStack.push({
            type: 'set',
            prop,
            prevValue: deepClone(prevValue),
            nextValue: deepClone(value)
          });
          this.redoStack = []; // 清空重做栈
        }
        return success;
      },
      deleteProperty: (target, prop) => {
        const prevValue = target[prop];
        const success = Reflect.deleteProperty(target, prop);

        if (success && !this.isUndoingRedoing) {
          this.undoStack.push({
            type: 'delete',
            prop,
            prevValue: deepClone(prevValue)
          });
          this.redoStack = [];
        }
        return success;
      }
    });
  }

  undo() {
    if (!this.undoStack.length) return;

    this.isUndoingRedoing = true;
    const operation = this.undoStack.pop();

    switch (operation.type) {
      case 'set':
        // 执行逆向操作
        this.redoStack.push({
          type: 'set',
          prop: operation.prop,
          prevValue: deepClone(this.state[operation.prop]),
          nextValue: operation.prevValue
        });
        this.state[operation.prop] = operation.prevValue;
        break;

      case 'delete':
        this.redoStack.push({
          type: 'delete',
          prop: operation.prop,
          prevValue: deepClone(this.state[operation.prop])
        });
        this.state[operation.prop] = operation.prevValue;
        break;
    }

    this.isUndoingRedoing = false;
  }

  redo() {
    // 类似 undo 的反向实现
  }
}

// 使用示例
const manager = new StateManager({ counter: 0 });
manager.state.counter = 1; // 记录操作
manager.undo(); // 回退到 0

最佳实践

  • 结构共享优化:使用 Immutable.js 或 Immer 避免深拷贝性能问题
  • 操作批处理:实现 transaction() 方法合并连续操作
  • 内存控制:设置历史栈最大长度防止内存泄漏
  • 变更通知:集成观察者模式触发 UI 更新

常见错误

错误类型后果解决方案
直接修改原状态破坏不可变性导致历史状态污染始终返回新对象
未处理嵌套对象深层属性变更无法追踪递归创建 Proxy
忽略 Symbol 属性特殊属性操作遗漏在 Proxy 中处理 ownKeys 陷阱
循环引用处理不当深拷贝时栈溢出使用 WeakMap 检测循环引用

扩展知识

  • 与 Redux 对比:Redux 需要显式 action 和 reducer,本方案实现隐式操作追踪
  • CRDT 算法:分布式场景下的高级状态同步方案
  • 时间旅行调试:Redux DevTools 实现原理
  • Proxy 局限:无法拦截直接操作(如数组 push/pop),需配合重写数组方法

性能优化策略

// 使用结构共享的优化克隆
function optimizedClone(obj) {
  if (typeof obj !== 'object' || obj === null) return obj;

  // 数组特殊处理
  if (Array.isArray(obj)) return [...obj];

  // 使用 Object.create 保持原型链
  const clone = Object.create(Object.getPrototypeOf(obj));

  for (const key in obj) {
    if (Object.hasOwnProperty.call(obj, key)) {
      clone[key] = obj[key];
    }
  }

  return clone;
}