• IoT Studio可视化搭建平台编辑历史功能的思考与探索


    简介: 在前端可视化搭建领域中“重做”和“撤销”这两个功能已经是标配中的标配,毕竟只要有用户行为的地方就可能会有出错,这两个功能无疑就是为用户提供了“后悔药”。目前有各种各样的可视化搭建平台,本文介绍IoT Studio可视化搭建平台在编辑历史功能上的设计与思考。

    image.png

    作者 | 远坂

    来源 | 阿里技术公众号

    一 背景

    在前端可视化搭建领域中“重做”和“撤销”这两个功能已经是标配中的标配,毕竟只要有用户行为的地方就可能会有出错,这两个功能无疑就是为用户提供了“后悔药”。目前有各种各样的可视化搭建平台,本文介绍IoT Studio可视化搭建平台在编辑历史功能上的设计与思考。

    image.png

    二 实现思路

    1 页面DSL的维护

    在IoT Studio可视化搭建平台中,我们通过页面的抽象语法树来维护页面状态,页面信息和组件信息都记录在对应节点上:

    image.png

    PageNode: {
      componentName: 'page1',
      id: 'page1',
      props: {},
      children: [
        ComponentNode: {
            componentName: 'component1',
            id: 'component1',
            props: {
               800,
              height: 1000,
              color: '#ffffff'
            },
            children: []
        },
        ComponentNode: {
            componentName: 'component2',
            id: 'component2',
            props: {},
            children: []
        },
        ComponentNode: {
            componentName: 'component3',
            id: 'component3',
            props: {},
            children: []
        },
      ]
    }
    在页面保存时,页面配置会作为JSON文件上传至OSS。

    2 重做与撤销

    快照法

    在每次编辑页面时,将页面的信息进行深拷贝存入历史记录中。在进行重做和撤销时从历史记录中取出对应的快照,用快照代替当前页面状态,即可完成一次历史记录的操作。

    在这种方法下,通常使用一个指针来指向当前的页面状态。如下图:

    image.png

    进行后退操作后,指针指向之前的某次快照,页面恢复到P3时的状态:

    image.png

    再次进行编辑时,指针指向新的状态P5 :

    image.png

    快照法的特点:
    1. 实现比较简单,页面信息全量进行深拷贝即可。
    2. 历史记录之间的切换灵活。
    3. 当页面信息很大时,十分占用存储空间。

    指令法

    IoT Studio使用的是这种方法。

    我们为每一次操作定义两个方法:execute与undo,以及将“操作”抽象为Operation。

    在execute中执行这次操作的正向操作,在undo中实现逆向操作。

    export abstract class Operation<T = void> {  
      /**
       * 逆向操作
       */
      protected abstract undo(): T;
    
      /**
       * 正向操作
       */
      protected abstract execute(): T;
    }
    
    

    每进行一次编辑操作,其实就是创建一次Operation并执行其execute方法,随后如果需要撤销就执行其undo方法。

    image.png

    指令法的特点:

    1. 相对快照法,在页面配置复杂时,能节省不少存储空间。
    2. 不同的Operation其execute和undo逻辑很可能会不一样,有一定的逻辑开发成本。
    3. 跨多个历史记录的重做或撤销,需要执行他们之前所有的execute或undo。例如,上图中如果从O3到O1需要执行2次undo。这一点没有快照法便利。

    3 实现细节

    在上文里提到了IoT Studio使用的是指令法。

    Transation

    在实际业务开发中,很多场景会涉及到一次性编辑多个组件,即涉及多个Operation实例。于是在Operation基础上有了Transaction——事务的概念,Transaction下维护了一份Operation实例List,每当有execute或者undo执行时,会遍历Operation List中的Operation实例执行其execute或undo方法。

    image.png

    双向链表

    IoT Studio中的操作历史是基于双向链表实现的,每个链表节点维护一个Transaction实例。链表节点末端的execute结果既是最新的操作历史。

    链表之前通过forwardCurrent和backforwardCurrent方法进行节点状态的切换。

    image.png

    Class Manager {
      backwardCurrent(): boolean {
          if (this._current?.prev) {
            this._current.value.operation.undo();
    
            this._current = this._current.prev;
            this._validLength -= 1;
            return true;
          }
          return false;
      }
    
      forwardCurrent(): boolean {
        if (this._current?.next) {
          this._current.next.value.operation.execute();
    
          this._current = this._current.next;
          this._validLength += 1;
          return true;
        }
        return false;
      }
    
      addAfterCurrent(item: OperationResult<any>) {
         if (nextNode) {
           nextNode.prev = undefined;
           this._length = this._validLength;
         }
    
         this._current.next = { value, prev: this._current };
         this._current = this._current.next;
       }
    }

    每当有新的编辑操作时,会通过addAfterCurrent插入新的节点。

    image.png

    4 总结

    Operation是实现重做和撤销的最小指令实例,通过Operation不同子类实现不同的execute和undo方法,从而实现重做和撤销的具体逻辑。

    Transaction中维护了Operation实例数组,我们在进行业务逻辑开发中对组件进行属性设置时是以Transaction实例为单位进行业务逻辑开发。

    维护了一个双向链表来对Transaction实例进行管理,从而实现可视化搭建的操作历史功能。

    image.png

    三 探索

    在实现思路中我们提到了“快照法”和“指令法”,对比两者的优缺点,不难发现主要矛盾是在体积与维护成本上。那么有没有一种办法能兼顾二者的优点呢?下面两个工具可以提供一些思路:

    immutable.js + 快照法

    在JS中对象是引用赋值,在保存对象时往往会使用深拷贝规避这个问题,但是这样会造成CPU和内存的浪费,这也是快照法的缺点所在。

    image.png

    immutable使用持久化数据结构,在使用旧数据创建新数据的时候,会保证旧数据同时可用且不变,同时为了避免深度复制复制所有节点的带来的性能损耗,immutable使用了结构共享,即如果对象树种的一个节点发生变化,只修改这个节点和受他影响的父节点,其他节点则共享。

    在实现操作历史功能时,使用immutable存储数据,能解决数据复用的问题。immutable.js + 快照法可以组合使用。据我所知公司的@ali/visualengine使用的就是这个方案。

    Git

    每次我们运行 git add 和 git commit 命令时,Git 所做的工作实质就是将被改写的文件保存为数据对象, 更新暂存区,记录树对象。

    image.png

    我们在使用git维护项目时,理论上随着git commit的次数越来越多,文件对象会越拉越大,但实际上体积并没有变的很大。事实上git在权衡时间和空间后帮我们做了部分优化,较早的版本会保存diff,较新的本会保存全量数据对象。
    Git 是如何做到这点的?Git 打包对象时,会查找命名及大小相近的文件,并只保存文件不同版本之间的差异内容。 你可以查看包文件,观察它是如何节省空间的。
    同样有趣的地方在于,第二个版本完整保存了文件内容,而原始的版本反而是以差异方式保存的——这是因为大部分情况下需要快速访问文件的最新版本。最妙之处是你可以随时重新打包。Git 时常会自动对仓库进行重新打包以节省空间。当然你也可以随时手动执行 git gc 命令来这么做。

    原文链接

    本文为阿里云原创内容,未经允许不得转载。 

  • 相关阅读:
    【bzoj2280】[Poi2011]Plot 二分+倍增+二分+最小圆覆盖
    【bzoj1336/1337/2823】[Balkan2002]Alien最小圆覆盖 随机增量法
    【bzoj4999】This Problem Is Too Simple! 树链剖分+动态开点线段树
    【bzoj3435】[Wc2014]紫荆花之恋 替罪点分树套SBT
    【bzoj3217】ALOEXT 替罪羊树套Trie树
    【bzoj3065】带插入区间K小值 替罪羊树套权值线段树
    【bzoj4012】[HNOI2015]开店 动态点分治+STL-vector
    【bzoj3924】[Zjoi2015]幻想乡战略游戏 动态点分治
    【bzoj1095】[ZJOI2007]Hide 捉迷藏 动态点分治+堆
    【bzoj3329】Xorequ 数位dp+矩阵乘法
  • 原文地址:https://www.cnblogs.com/yunqishequ/p/15379021.html
Copyright © 2020-2023  润新知