vue3的撤销与重做

前言

一个低代码项目遇到的撤销重做功能,基于vue3实现。

实现步骤

  1. 通过pinia来存储历史记录数组(arr)、目前页面所展示的对应的索引(index)、现在是否要生成快照(isSnapshot),作用是防止撤销重做的时候也会添加快照,因为撤销重做的时候会触发watch里面的方法、最大能够存储的数据数(maxStep)。在pinia定义几个方法。
  2. 第一个生成快照,watch来监听页面元素数组的变化,变化时调用方法,进入该方法后判断是否能生成快照,不可以将isSnapshot=true再退出,可以先判断一下index是否在队列的尾部,不在需要将index后面的元素删除掉,再将页面元素数组深复制push到历史记录数组里面。
	addSnapshot() {
          if (this.isSnapshot) {
              this.isFull();//判断是否溢出 溢出删除一个
              let n = this.snapshotData.length;
              // 当前索引不在开头需要恢复时
              if (this.curIndex < n-1) {
                  this.snapshotData.splice(this.curIndex+1);
              }
              // elStore().els为页面元素数组
              this.snapshotData.push(JSON.stringify(elStore().els));
              this.curIndex++;
          }
          this.isSnapshot = true;
      }
  1. 第二个判断是否溢出,溢出shift出一个元素。
        // 判断是否溢出
        isFull() {
            if (this.snapshotData.length == this.maxStep) {
                this.snapshotData.shift(0);
            }
        },
  1. 第三个撤销,如果index>0,证明现在可以撤销,然后把isSnapshot=false,再将index减1,后将index指向的元素数组渲染到页面。
		// undo撤销
        undo() {
            if (this.curIndex > 0) {
                this.isSnapshot = false;
                let t = JSON.parse(this.snapshotData[--this.curIndex]);
                for (let i = 0; i < t.length; i++) {
                    elStore().els[i] = t[i];
                }
            }
        },
  1. 第四个重做,如果没有到数组的最尾端证明可以重做,然后把isSnapshot=false,再将index加1,后将index指向的元素数组渲染到页面。
        // record 恢复
        record() {
            // 判断是不是到尾部,有才可以恢复 
            if (this.curIndex < this.snapshotData.length-1) {
                this.isSnapshot = false;
                let t = JSON.parse(this.snapshotData[++this.curIndex]);
                for (let i = 0; i < t.length; i++) {
                    elStore().els[i] = t[i];
                }
            }
        }

注意点

虽然简单方便但可能会引起数据的丢失。
缺点:

  1. 使用JSON.Stringify 转换的数据中,如果包含 function,undefined,Symbol,这几种类型,不可枚举属性,JSON.Stringify序列化后,这个键值对会消失。
  2. 转换的数据中包含 NaN,Infinity 值(含-Infinity),JSON序列化后的结果会是null。
  3. 转换的数据中包含Date对象,JSON.Stringify序列化之后,会变成字符串。
  4. 转换的数据包含RegExp 引用类型序列化之后会变成空对象。
  5. 无法序列化不可枚举属性。
  6. 无法序列化对象的循环引用,(例如: obj[key] = obj)。
  7. 无法序列化对象的原型链。

总体代码

// 监听事件,这里会遇到频繁触动的问题,用防抖解决
    watch(useElStore.els, debounce(() => {
      snapshotStore.addSnapshot();
    }, 400), { immediate: true });
    
 // 撤销
    function cancel() {
      changeRectShow();
      snapshotStore.undo();
    }

    // 重做
    function record() {
      changeRectShow();
      snapshotStore.record();
    }
// 防抖代码
export default (fn,delay) =>{
    let t = null;
    return () => {
        if (t != null) {
            clearInterval(t);
        }
        t = setTimeout(() => {
            fn();
        },delay)
    }
}
// snapshot仓库
import { defineStore } from 'pinia'
import elStore from './index'
export default defineStore('snapshot', {
    state() {
        return {
            snapshotData: [],// 快照数据
            maxStep: 30,// 最大能够存储的数据数
            curIndex: -1,// 当前所在下标
            isSnapshot: true,// 是否可以保存
        }
    },
    actions: {
        // 添加快照
        addSnapshot() {
            if (this.isSnapshot) {
                this.isFull();//判断是否溢出 溢出删除一个
                let n = this.snapshotData.length;
                // 当前索引不在尾部,说明进行了撤销,需要删除后面的元素
                if (this.curIndex < n-1) {
                    this.snapshotData.splice(this.curIndex+1);
                }
                this.snapshotData.push(JSON.stringify(elStore().els));
                this.curIndex++;
            }
            this.isSnapshot = true;
        },
        // 判断是否溢出
        isFull() {
            if (this.snapshotData.length == this.maxStep) {
                this.snapshotData.shift(0);
            }
        },
        // undo撤销
        undo() {
            if (this.curIndex > 0) {
                this.isSnapshot = false;
                let t = JSON.parse(this.snapshotData[--this.curIndex]);
                for (let i = 0; i < t.length; i++) {
                    elStore().els[i] = t[i];
                }
            }
        },
        // record 恢复
        record() {
            // 判断是不是到尾部,有才可以恢复 
            if (this.curIndex < this.snapshotData.length-1) {
                this.isSnapshot = false;
                let t = JSON.parse(this.snapshotData[++this.curIndex]);
                for (let i = 0; i < t.length; i++) {
                    elStore().els[i] = t[i];
                }
            }
        }
    }
})

你可能感兴趣的:(javascript,vue.js,前端)