天天看點

IoT Studio可視化搭建平台編輯曆史功能的思考與探索

IoT Studio可視化搭建平台編輯曆史功能的思考與探索

作者 | 遠坂

來源 | 阿裡技術公衆号

一 背景

在前端可視化搭建領域中“重做”和“撤銷”這兩個功能已經是标配中的标配,畢竟隻要有使用者行為的地方就可能會有出錯,這兩個功能無疑就是為使用者提供了“後悔藥”。目前有各種各樣的可視化搭建平台,本文介紹IoT Studio可視化搭建平台在編輯曆史功能上的設計與思考。

IoT Studio可視化搭建平台編輯曆史功能的思考與探索

二 實作思路

1 頁面DSL的維護

在IoT Studio可視化搭建平台中,我們通過頁面的抽象文法樹來維護頁面狀态,頁面資訊群組件資訊都記錄在對應節點上:

IoT Studio可視化搭建平台編輯曆史功能的思考與探索
PageNode: {
  componentName: 'page1',
  id: 'page1',
  props: {},
  children: [
    ComponentNode: {
        componentName: 'component1',
        id: 'component1',
        props: {
          width: 800,
          height: 1000,
          color: '#ffffff'
        },
        children: []
    },
    ComponentNode: {
        componentName: 'component2',
        id: 'component2',
        props: {},
        children: []
    },
    ComponentNode: {
        componentName: 'component3',
        id: 'component3',
        props: {},
        children: []
    },
  ]
}           

在頁面儲存時,頁面配置會作為JSON檔案上傳至OSS。

2 重做與撤銷

快照法

在每次編輯頁面時,将頁面的資訊進行深拷貝存入曆史記錄中。在進行重做和撤銷時從曆史記錄中取出對應的快照,用快照代替目前頁面狀态,即可完成一次曆史記錄的操作。

在這種方法下,通常使用一個指針來指向目前的頁面狀态。如下圖:

IoT Studio可視化搭建平台編輯曆史功能的思考與探索

進行後退操作後,指針指向之前的某次快照,頁面恢複到P3時的狀态:

IoT Studio可視化搭建平台編輯曆史功能的思考與探索

再次進行編輯時,指針指向新的狀态P5 :

IoT Studio可視化搭建平台編輯曆史功能的思考與探索

快照法的特點:

  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方法。

IoT Studio可視化搭建平台編輯曆史功能的思考與探索

指令法的特點:

  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方法。

IoT Studio可視化搭建平台編輯曆史功能的思考與探索

雙向連結清單

IoT Studio中的操作曆史是基于雙向連結清單實作的,每個連結清單節點維護一個Transaction執行個體。連結清單節點末端的execute結果既是最新的操作曆史。

連結清單之前通過forwardCurrent和backforwardCurrent方法進行節點狀态的切換。

IoT Studio可視化搭建平台編輯曆史功能的思考與探索
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插入新的節點。

IoT Studio可視化搭建平台編輯曆史功能的思考與探索

4 總結

Operation是實作重做和撤銷的最小指令執行個體,通過Operation不同子類實作不同的execute和undo方法,進而實作重做和撤銷的具體邏輯。

Transaction中維護了Operation執行個體數組,我們在進行業務邏輯開發中對元件進行屬性設定時是以Transaction執行個體為機關進行業務邏輯開發。

維護了一個雙向連結清單來對Transaction執行個體進行管理,進而實作可視化搭建的操作曆史功能。

IoT Studio可視化搭建平台編輯曆史功能的思考與探索

三 探索

在實作思路中我們提到了“快照法”和“指令法”,對比兩者的優缺點,不難發現主要沖突是在體積與維護成本上。那麼有沒有一種辦法能兼顧二者的優點呢?下面兩個工具可以提供一些思路:

immutable.js + 快照法

在JS中對象是引用指派,在儲存對象時往往會使用深拷貝規避這個問題,但是這樣會造成CPU和記憶體的浪費,這也是快照法的缺點所在。

IoT Studio可視化搭建平台編輯曆史功能的思考與探索
immutable使用持久化資料結構,在使用舊資料建立新資料的時候,會保證舊資料同時可用且不變,同時為了避免深度複制複制所有節點的帶來的性能損耗,immutable使用了結構共享,即如果對象樹種的一個節點發生變化,隻修改這個節點和受他影響的父節點,其他節點則共享。

在實作操作曆史功能時,使用immutable存儲資料,能解決資料複用的問題。immutable.js + 快照法可以組合使用。據我所知公司的@ali/visualengine使用的就是這個方案。

Git

每次我們運作 git add 和 git commit 指令時,Git 所做的工作實質就是将被改寫的檔案儲存為資料對象, 更新暫存區,記錄樹對象。
IoT Studio可視化搭建平台編輯曆史功能的思考與探索

我們在使用git維護項目時,理論上随着git commit的次數越來越多,檔案對象會越拉越大,但實際上體積并沒有變的很大。事實上git在權衡時間和空間後幫我們做了部分優化,較早的版本會儲存diff,較新的本會儲存全量資料對象。

Git 是如何做到這點的?Git 打包對象時,會查找命名及大小相近的檔案,并隻儲存檔案不同版本之間的差異内容。 你可以檢視封包件,觀察它是如何節省空間的。

同樣有趣的地方在于,第二個版本完整儲存了檔案内容,而原始的版本反而是以差異方式儲存的——這是因為大部分情況下需要快速通路檔案的最新版本。最妙之處是你可以随時重新打包。Git 時常會自動對倉庫進行重新打包以節省空間。當然你也可以随時手動執行 git gc 指令來這麼做。

Python 腳本入門

Python 腳本入門帶着各位運維工程師掌握了在運維過程中需要掌握的核心的 Python 文法和子產品;讓運維工程師可以通過 Python 來完成自己的腳本的編寫。

點選這裡

,加入學習~

繼續閱讀