自己手写撤销和回退方式

210 阅读2分钟

撤销和回退的实现

大家应该都使用过浏览器的后退和前进功能,它就是我们今天的主题:撤销和回退。其实不光是在浏览器里面,在众多的工具软件内也都有类似的功能,远的不说,例如:vscode、ppt

第一步:先把静态页面画好

功能就是点击abc三种球可以展示在栈中,栈有一个特点他对数据是后进先出的正好可以做我们的操作,我定义了两个栈分别是撤销栈和回退栈方便我们记录操作进行返回

<div>
  <div>
    点击添加:
    <div className="circleBox">
      <div className="red circle" onClick={() => onClickCircle("red")} />
      <div className="green circle" onClick={() => onClickCircle("green")} />
      <div className="blue circle" onClick={() => onClickCircle("blue")} />
    </div>
  </div>
  <div>
    <button onClick={onUndo} disabled={undoStack?.length === 0}>撤销</button>
    <button onClick={onRedo} disabled={redoStack?.length === 0}>回退</button>
  </div>
  <div>
    <div>
      <p>当前的视图</p>
      <div className={`circle ${currentView}`} />
    </div>
    <div className="listBox">
      <div>
        <p>可撤销列表</p>
        <div className="undoList">
          {undoStack?.map((record) => (
  <div key={record.timestamp} className={`circle ${record.type}`} />
))}
        </div>
      </div>
      <div>
        <div>
          <p>可重做列表</p>
          <div className="redoList">
            {redoStack?.map((record) => (
	  <div key={record.timestamp} className={`circle ${record.type}`} />
		))}
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

第二步:定义hooks

分别是记录当前是什么图形,撤销栈数据和回退栈数据

const [currentView, setCurrentView] = useState("");
const [undoStack, setUndoStack] = useState([]);
const [redoStack, setRedoStack] = useState([]);

第三步:操作函数实现

添加图形进入撤销栈中

function onClickCircle(type) {
  const record = createRecord(type);
  setUndoStack([...undoStack, record]);
  setRedoStack([]);
  updateData();
}

记录当前添加的图形颜色,还有时间戳,这个需求简单其实不需要记录,如果是对复杂数据进行操作可以通过时间戳进行对比

function createRecord(type) {
  return {
    type: type,
    timestamp: new Date().getTime()
  };
}

onUndo函数会将最近的记录从撤销栈中弹出,并将其添加到回退栈中。如果撤销栈为空,则不执行任何操作。当用户点击“回退”按钮时,onRedo函数会将最近的记录从回退栈中弹出,并将其添加到撤销栈中。如果重做栈为空,则不执行任何操作。这两个函数都会调用updateData函数来更新当前视图和可撤销/可重做列表的状态

function onUndo() {
  const record = undoStack.pop();
  if (record) {
    setRedoStack([...redoStack, record]);
    updateData();
  }
}


function onRedo() {
  const record = redoStack.pop();
  if (record) {
    setUndoStack([...undoStack, record]);
    updateData();
  }
}

总结:

这个功能可以为你的Web应用程序带来很大的价值,因为它可以让用户在操作时更加自由和放心

在实现这个功能时,需要注意以下几点:

  1. 确保在更新状态时不会出现数据竞争。这意味着你需要遵循React的规则,即不要直接修改状态,而是使用setState函数来更新它。
  2. 在组件中使用useState来定义状态。这样做可以让你更好地控制状态的生命周期,并避免不必要的内存泄漏。
  3. 使用一些辅助函数来帮助实现撤销/重做功能。这些函数可以帮助你创建记录、更新视图和列表等。
  4. 在渲染组件时,确保正确地渲染圆形、按钮和列表。这样可以让用户更好地理解当前的状态,并且更容易使用撤销/重做功能。