JavaScript 撤销还原实现

814 阅读1分钟

命令模式

class AddCommand {
  constructor(value) {
    this.value = value;
  }

  execute(state) {
    this.redoValue = state;
    state = this.value;
    return state;
  }

  undo(state) {
    state = this.redoValue;
    return state;
  }
}

class Manager {
  constructor() {
    this.state = undefined;
    this.commands = [];
    this.undoCommands = [];
  }

  execute(command) {
    this.state = command.execute(this.state);
    this.commands.push(command);
    this.undoCommands = [];
    this.log();
  }

  undo() {
    const command = this.commands.pop();
    this.state = command.undo(this.state);
    this.undoCommands.push(command);
    this.log();
  }

  redo() {
    const command = this.undoCommands.pop();
    this.state = command.execute();
    this.commands.push(command);
    this.log();
  }

  log() {
    const manger = new this.constructor();
    manger.state = this.state;
    manger.commands = this.commands.slice(0);
    manger.undoCommands = this.undoCommands.slice(0);
    console.log(manger);
  }
}

const manger = new Manager();
manger.log(); // => undefined
manger.execute(new AddCommand(1)); // => 1
manger.execute(new AddCommand(2)); // => 2
manger.execute(new AddCommand(3)); // => 3
manger.undo(); // => 2
manger.undo(); // => 1
manger.redo(); // => 2
manger.execute(new AddCommand(4)); // => 4

数据快照

import { createStore } from "redux";

function operationReducer(processor) {
  return (state, action) => {
    state = { ...state };

    // undo、redo 的拦截
    if (action.type === "undo") {
      state.head = Math.max(0, state.head - 1);
      return state;
    }
    if (action.type === "redo") {
      state.head = Math.min(state.timeline.length - 1, state.head + 1);
      return state;
    }

    const { timeline, head, limit } = state;

    // operation处理
    const snapshot = processor(timeline[head], action);
    // timeline处理
    if (snapshot !== undefined) {
      state.timeline = timeline.slice(0, head + 1);
      state.timeline.push(snapshot);
      state.timeline = state.timeline.slice(-limit);
      state.head = state.timeline.length - 1;
    }
    return state;
  };
}

function processor(state, action) {
  if (action.type === "add") {
    return action.value;
  }
}

const initialManageState = {
  timeline: [],
  head: -1,
  limit: 20
};

const store = createStore(operationReducer(processor), initialManageState);

store.subscribe(() => console.log(store.getState()));
console.log(store.getState()); // => []
store.dispatch({ type: "add", value: 1 }); // => [1]
store.dispatch({ type: "add", value: 2 }); // => [1, 2]
store.dispatch({ type: "add", value: 3 }); // => [1, 2, 3]
store.dispatch({ type: "undo" }); // => [1, 2]
store.dispatch({ type: "undo" }); // => [1]
store.dispatch({ type: "redo" }); // => [1, 2]
store.dispatch({ type: "add", value: 4 }); // => [1, 2, 4]

总结

命令模式代码更加健壮,数据快照代码更加优雅。

参考资料

网易云音乐