react可视化实现撤销、回退功能(undo/redo)

业务需求:
首先看下实际操作:我们需要在每次改变画布中元素位置或属性后实现操作的撤销,撤销之后还要能回退。


bg (2).gif

这个看似简单的需求,思路不对的话,会走很多弯路,比如之前我用一个数组加两个游标(变量)试图去解决这个问题,但根本没可能。实现这个功能需要一些算法和栈的运用。

我们需要两个栈来实现这个操作:分别是撤销栈和回退栈。

整体流程:
1.每当我们开始拖拽画布元素,或者开始缩放之前,我们要保存此次操作时刻的屏幕快照,在本系统中,组件都以json数组的形式来渲染。所以我们需要保存的就是当前画布的组件json数组。.将当前画布的json数组作为一个整体push进撤销栈,表示记录本次操作。

2.我们拖动元素这个动作结束了,当前画布的json数组必然发生了更新。现在我们想撤销这次拖动操作。点击撤销后,先将当前画布的json数组push到回退栈中,用撤销栈中最后一个json数组来setstate重新渲染画布元素(第一步push的那个json数组)。再使用数组pop方法,删除撤销栈中最后一个json数组。

3.回退时先将当前画布的json数组push到撤销栈中,用回退栈中最后一个json数组来setstate重新渲染画布元素(第二步push的那个json数组)。再使用数组pop方法,删除回退栈中最后一个json数组。

有点绕是不是?


4f73703bd58543449a08ef6d2b2da1af.png

上图:

再看下控制台输出来理解一下:


adcaf891cd4341f9b9f6f951451cd181.gif

撤销回退实现部分:

import { cloneDeep } from 'lodash';
import { useModel } from 'umi';
 
// 最大回退操作步骤
const maxStep = 60;
 
const undoQueue: any = [];
let redoQueue: any = [];
 
// 实现撤销回退队列的管理功能
const QueueManager = () => {
  const { components, setComponents } = useModel('visualPage');
  const saveSnap = () => {
    const snap = cloneDeep(components);
    if (undoQueue.length >= maxStep) {
      undoQueue.shift();
    }
    undoQueue.push(snap);
    // console.log('queue', undoQueue, redoQueue);
    // 注意!!每次执行命令时清空重做栈
    redoQueue = [];
  };
 
  const undo = () => {
    const snap = cloneDeep(components);
    const c = cloneDeep(undoQueue[undoQueue.length - 1]);
    setComponents(c);
    redoQueue.push(snap);
    undoQueue.pop();
    // console.log('undo', undoQueue, redoQueue);
  };
 
  const redo = () => {
    const snap = cloneDeep(components);
    const c = cloneDeep(redoQueue[redoQueue.length - 1]);
    setComponents(c);
    undoQueue.push(snap);
    redoQueue.pop();
    // console.log('redo', undoQueue, redoQueue);
  };
 
  return {
    saveSnap,
    undo,
    redo,
    undoQueue,
    redoQueue,
  };
};
 
 
export default QueueManager;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容