雲鳳蝶可視化搭建的推導與實現

blank

雲鳳蝶可視化搭建的推導與實現

前言

從古老的桌面軟件領域的WPF、JavaFX,到移動端與時俱進的SwiftUI、JetPack Compose,再到Salesforce、OutSystems、Mendix 等一眾hpaPaaS (High-productivity Application platform as a service),一直以來許多產品都在嘗試通過可視化搭建的手段來降低GUI 應用的研發門檻,提高生產效率。而云鳳蝶則是一個新的挑戰者。

本文將以概覽性的視角來介紹雲鳳蝶在低代碼+可視化搭建這條路上遇到的問題與解決方案,包括:

  • 中後台Web 應用搭建有哪些關鍵要素?
  • 可視化編輯器如何對齊Pro Code 下視圖DSL 的表達能力?
  • 交互邏輯與狀態如何進行組織與聚合?
  • 如何基於可視化底盤去探索10 倍效能提升?

一、雲鳳蝶產品介紹

雲鳳蝶產品介紹https://www.zhihu.com/video/1175856121427955712

二、可視化搭建的關鍵要素

blank

如上圖左側所示是一個簡單網頁,一個Tabs 內嵌了一個Form 表單,在普通用戶的視角里看到的世界可能是扁平的,但是在開發者的視角基本都可以看出來右側的一顆組件樹。

一個組件化的搭建平台其實本質就是一條組件組裝流水線,其核心目的就是如何多快好省地幫助用戶製作出這顆樹。

那麼讓我們將視角聚焦到“組件”這個東西上,在當前大多數前端框架體系下,組件是一個滿足f: props => view協議的函數,它的入參一般是一些簡單的抽象好的配置,返回值則是使用某種視圖描述語法(HTML、Virtual DOM)來描述的一塊視圖,而其計算過程可以使用任意框架或語言來實現。

如此看來,組件還牽扯到函數和運算過程,好複雜...

而實際上我們可以先嘗試做一個職責切分,因為現如今在螞蟻前端的Pro Code 研發體系下,Basement 負責研發流程,Gitlab 負責代碼管理, tnpm 負責管理模塊,Bigfish 負責組件開發規範。所有這些都已經非常成熟,我們應該把專業的事情交給專業的人來做,盡量站在這些成熟的體系之上,也就是:

“讓上帝的歸上帝,凱撒的歸凱撒”

因此最終雲鳳蝶決定將Pro Code 世界下的組件作為原材料去消費,自身專心去做一條高效且全能的組裝流水線。一條組裝流水線其最小功能閉環至少需要有:

  • 組件的導入與二次拼裝能力
  • 組件的入參屬性定制能力
  • 組件的邏輯編排與聯動能力

細化來看,每一個其實都是相當複雜的課題:

  • 組件的導入與二次拼裝能
    • 如何實現中心化的組件註冊與加載體系
    • 如何實現編輯與預覽時實時更新
    • 如何控制組件之間的加載順序
    • 多組件的公共依賴如何去重
    • 如何處理組件的不兼容升級
  • 組件的入參屬性定制能力
    • 屬性面板如何表達antd 的所有props 類型
    • 如何支撐對屬性的擴展能力
      • 對值的類型的擴展能力
        • JS 基本類型
        • 業務類型:Moment、Regexp、URL、Function、Email、Image...
      • 對值的來源的擴展能力
        • 靜態值
        • 數據綁定(單向,雙向,一次性)
        • 異步動態數據源(HTTP/TR)
        • 第三方圖片素材庫...
  • 組件的邏輯編排與聯動能力
    • 組件之間組合:如何解決不同屏幕尺寸下的多組件位置關係變化問題
    • 組件之間聯動:如何支持外置共享狀態的管理與消費

這其中每一個點可能都需要一篇文章的筆墨才能介紹清楚,因此本文只會是一篇目錄性質的綱要介紹,後續我們會陸續發表對更多實現細節的分享文章,敬請期待。

三、組件導入與製作

上一小節已經闡釋了雲鳳蝶需要有消費npm 世界的組件能力。如下圖所示,雲鳳蝶既支持從代碼導入也支持拖拽二次拼裝。

blank
blank

通過上述一套流程,我們可以實現快速導入一個npm 上已存的代碼組件到雲鳳蝶平台上使用,並且基於構建出的依賴Manifest 訊息和SystemJS Loader 可以實現對一些公共依賴的去重,加載順序的控制等等。

更為重要的是,這裡面出現了一份“雲鳳蝶組件定義”,這是一份非常重要的白皮書,它是我們在摸爬滾打接入antd所有組件之後,最終總結出來的對一個組件所有編輯時與運行時公共行為的上層建模與抽象,它指導了雲鳳蝶如何去理解一個組件的諸多表現。

四、組件屬性定制與擴展

對屬性值的定制

屬性編輯器的意圖是完成對組件的屬性定制,它本質屬於一個複雜的Schema Form 形態的配置器。但是以下圖中截取antd Table組件的部分的props的為例, ant.design/components/t

blank

可以看到:

  • 有大量props的值是非JavaScript基本類型
  • 有大量props的類型是聯合類型
  • 有少量props互相之間是邏輯關聯的,或者互斥

雲鳳蝶為此打造了一塊極其強大的屬性配置面板,力求完整的表達能力和良好的用戶體驗兼顧,後續文章會專門介紹,敬請期待。

對屬性值的擴展

即使將屬性面板的表單體驗打磨到極致,也只是還原了antd 的能力而已,要想得到比antd 更高的效能提升,還需要具備對原生屬性的擴展能力,因為屬性的多樣性就代表了組件能力的多樣性,比如:

  • 對值的類型的擴展:除了JS基本類型之外,是否可以自定義任意業務類型?比如:國際化文案,圖片素材庫等。
  • 對值的來源的擴展:除了直接在屬性面板裡面輸入靜態值之外,值是否可以來源於變量綁定?來自異步請求?來自數據源? 。

以雲鳳蝶其中一種最重要的對屬性值來源的擴展- binding(数据绑定)為例,它和Vue、Angular、小程序等技術體系下的數據綁定概念一脈相承,交互形式如下:

雲鳳蝶-數據綁定https://www.zhihu.com/video/1175861306061680640

那麼這種對props 的數據綁定最終是如何存儲與運行的呢?以Table 組件的一個props 範例:

blank

在Pro Code的世界裡,props應該是一顆普通的對象樹,雲鳳蝶通過定義一種{ $$__type, $$__body }的特殊對象結構實現了在一顆對象樹上任意位置去擴展複雜的業務實體類型。

每一種業務類型都是一個在雲鳳蝶系統上註冊過的“實體”,每一個實體都需要實現一個實體Loader,如下圖中的有顏色的節點全部是業務實體:

blank

為什麼數據格式不採用JSON Schema ?因為它寫起來和實現起來都繁瑣,體積臃腫。

在運行時真正渲染每一個組件之前,它的props配置數據都會經歷一個“翻譯”過程,雲鳳蝶的Runtime會掃描props對象樹,在任意節點一旦遇到滿足{ $$___type, $$__body }形態的Entity,就會轉而調用對應的Entity Loader 來翻譯該節點,具體操作一般是返回一個真正可交給原始組件運行的plain data 替代該節點,同時也允許Loader 去執行一些副作用,以數據綁定為例,副作用就可能包括監聽數據變化和更新渲染視圖。

一個Entity loader 的偽代碼:

blank

對屬性本身的擴展

除了上一小節提到的擴展antd props的配置形態,雲鳳蝶平台也會向所有組件的屬性面板添加一些通用高級屬性,如:是否渲染、是否隱藏、重複、通用樣式,懸浮提示,固釘,徽標,loading 加載中等等,每一個通用高級屬性都是Web 應用研發中某一類常見功能的抽象與封裝,力求所有能被模式化的Feature 都被沉澱下來成為一條通用路徑。

blank

而這些高級渲染屬性的實現原理則是利用HOC (Higher-Order Components)去包裹原始組件實現這些增強功能,如圖:

blank

五、組件的可視化組合與編排

組件的可視化編排會涉及幾個關鍵問題:

  • 編輯時拖拽組件的二維坐標如何形成嵌套組件樹
  • 屏幕尺寸變化的時候,組件自身,組件與其他組件之間的位置關係如何變化

第一個問題相對簡單,我們以是否被包含來決定父子關係。舉個例子,畫布上有如下[A, B, C, D] 四個元素。

blank

我們根據坐標和尺寸數據可以得到一個包含集合,例如:

  • A: [B, C, D] (A 包含了B,C,D)
  • B: [C, D] (B 包含了C,D)
  • C: [D] (C 包含了D)

然後進行第二輪算法,如果某個被包含的元素,不再被本集合內的其他元素包含,就可以認為它和宿主之間是屬於直接父子關係,得到:

  • A: [B] (A 直接包含B)
  • B: [C] (B 直接包含C)
  • C: [D] (C 直接包含D)

從而最終得到了A -> B -> C -> D的父子組件樹鏈路。當然真實情況中還要考慮浮層這種獨立圖層,以及非法父子關係等細節,但核心就是依據這個直接被包含的關係算法,我們可以將二維的畫布根據位置訊息建模成一顆組件樹。

解決了樹算法之後,要想解決屏幕尺寸變化的自適應問題我們就必須引入一套佈局系統來解決。

佈局系統

自由佈局

自由佈局是雲鳳蝶默認的佈局類型,使用體驗就像KeyNote 或者Sketch 等產品一樣自由拖拽,隨心擺放。

blank

自由拖拽可以解決固定屏幕尺寸下的元素擺放問題,但是當屏幕尺寸變化之後如何自適應?雲鳳蝶採用了一種類似iOS Auto Layout的系統,但是約束規則會更簡單直觀:

blank
blank
  • 寬度/高度
    • 固定:永遠不變
    • 自適應:隨著容器(父)的變大和變大
    • 適配內容:隨著內容(子)的變大而變大
  • 上下左右間距
    • 固定:永遠不變
    • 自適應:隨著容器(父)的變大而變大

根據靜態的坐標訊息再加上佈局系統的約束規則配置,雲鳳蝶運行時就可以將用戶的頁面翻譯成一個自適應的頁面,後續本系列會有專題文章介紹,在此只簡要概述整個算法的核心原理:

  • 編輯態用戶操作:
    • 自由拖拽:得到x(水平坐標)、 y(垂直坐標)、 w(寬)、 h(高)
    • 按需配置寬、高、間距的佈局約束規則
  • 編譯時:
    • 基於直接被包含算法,得到組件樹
    • 基於水平垂直軸是否交叉對整個頁面遞歸進行行列分割,將絕對定位的佈局系統轉換為相對佈局
blank
    • 對約束規則進行翻譯
  • 運行時
    • 一個普通的自適應頁面

彈性佈局

雲鳳蝶同樣支持將佈局類型從自由切換為彈性,彈性佈局更適合流式的頁面編排。

blank

blank

畫布體驗

當然解決了組件樹算法和佈局算法等基建問題之後, 留給可視化搭建平台的還有一個持久的挑戰是,如何打造一塊好用的畫布?因為如果自由拖拽做的不好,反而可能會導致效率低下,畫布這裡面涉及到海量的技術細節與產品設計,如:參考線、對齊、成組、分佈、拖拽、縮放,相對位置等等,本文暫不展開,後續會有專題文章介紹。

畫布的邏輯表達能力

雲鳳蝶沒有暴露類似HTML 或者JSX 的視圖DSL 給用戶,只提供了一塊平面的二維可視化畫布,那麼如何來表達視圖的條件,循環等邏輯呢?

提供“多狀態面板組件”表達if/else,switch/case 類能力

提供“重複”的高級屬性來表達forEach 的循環能力,提供“滿足條件才渲染”來表達條件能力

blank

六、組件的共享狀態與聯動

上文我們已經基本了解瞭如何通過一塊畫布生產一顆組件樹,那麼如果組件之間要聯動或者存儲狀態,這些邏輯該如何做呢?

前後端實現共享數據的方式一般都是通過共享內存或者打通通信渠道。前端的Redux,Mobx ,Dva 等架構也都是採取狀態外置的思路,用公共的Model 實例來存儲所有應用狀態,然後不同組件按需的讀寫,輔以一些工程化和最佳實踐的約束。

雲鳳蝶也不外如是,只是我們認為這些框架寫法太繁瑣,概念多,門檻高,雲鳳蝶對外置狀態管理的方案做了極致簡化。

如果使用過React的開發者可能對statesetState這兩個API非常熟悉,React教會我們開發一個組件只需要做三件事情:

  • 定義state
  • 使用state 數據來生成試視圖
  • 在必要的時候修改state, 以自動更新視圖

很明顯,修改state 之後隨之而來的vdom diff 與更新都屬於自動化流程,你不需要過多關注。

這非常符合現實世界的隱喻,我們下班回到家只會按下控制客廳燈的開關,而不會去細究背後的走線和電路控制信號流程。

可惜React 還是遺留了幾個問題交給用戶自由選擇,社區也有非常多不同的優秀解決方案:

  • 多組件如何共享狀態?需要搭配Context、Redux 等方案
  • setState 修改深層屬性的時候非常痛苦。需要搭配immer、 immutablejs 等方案

雲鳳蝶決定沿襲React state 的主體API 設計形態,但是要解決上面兩個問題:

  • store 外置:狀態是永遠standalone 獨立存在的,保證single source of truth
  • store mutable:no setState,no immutable,請用你最自然舒適的方式修改數據

最終雲鳳蝶決定為每個頁面都提供一個類似MMVM 架構下的Controller 角色的View Model 文件,其內容就是導出一個普通的TypeScript Class,而Class 的核心概念正好和狀態管理一一對應,美妙且自然:

  • 屬性(對應狀態)
  • 方法(對應修改狀態的行為)
blank

在頁面Controller 中定義好變量和方法之後,用戶可以通過屬性面板的交互操作:

  • 將組件的props綁定到Controller內的變量
  • 將組件的event綁定到Controller內的方法

用自然的Class 語法來聚合state + action,開發者無需再學習Redux 架構下effects,reducer 等概念和編寫大堆模板代碼,而且:

voila!可以做響應式:
組件屬性所綁定的變量發生變化,組件會精確自動更新

Reactive 理念

上文提到過,雲鳳蝶的狀態管理目標是自動化,即只需要修改狀態,不需要關注如何更新視圖,那麼如何實現這種效果呢?

核心架構就是Reactive (響應式)和Observable (可觀測)。整個雲鳳蝶的Controller Class 都是observable 的,也就是這個class 是被Proxy 化的,雲鳳蝶的Runtime 可以追踪任意屬性的讀寫。而屬性面板的數據綁定( Button.props.text = $model.buttonText )的過程實質是在收集依賴,從而可以實現數據變化時檢查依賴列表自動精確更新視圖,達到響應式。

關於Observable和依賴收集理念的優越性,下面這兩張來自WWDC 2019 - Session 204:Introducing SwiftUI: Building Your First App的PPT詮釋得相當好:

blank
blank

GUI 程序通常使用場景是處理一個長時間段的用戶交互過程,這其中不可避免會涉及到許多用戶輸入訊息,視圖臨時狀態的存儲,此外整個頁面樹上可能有多至數以百計的元素(如一個表單或表格)之間要互相通信和聯動。

如果按照傳統的事件驅動的編程模型,依靠程序員的大腦去掌控所有數據到視圖,數據到數據之間的衍生依賴關係,腦力負擔是非常大的,開發者小心翼翼維護著所有關聯邏輯,而一旦這個依賴複雜度超越了人腦的算力,就會導致關聯關係不正確,也就是Bug 的產生。

而“響應式”就是將這個依賴關係的維護交由框架來處理,開發者只需要“聲明”出依賴關係,而且這個“聲明”是指在描述業務邏輯的過程中來順帶地描述出這種依賴關係,並不需要為此大動干戈,比如:

  • State => View在定義狀態到視圖的過程中,生產視圖消費(getter)了哪些數據,就可以認為是該視圖對該數據建立了依賴(典型場景是雲鳳蝶屬性面板的數據綁定)
blank
  • State => Drived State計算屬性的定義過程中,該屬性的結果依賴了哪些中間數據,就可以認為是該屬性對其他屬性建立了依賴

在這個理論體系下,State始終是single source of truth,視圖只是數據的一個衍生值(Derived Value)

blank

雲鳳蝶採用響應式的狀態管理方案,從而使應用開發者用“狀態驅動”的思維替代傳統的命令驅動,開發者的腦海中始終在思考此刻控制我的視圖的數據狀態是如何,而背後衍生的視圖如何更新,需不需要create 一個div 然後append 到哪個dom 上去,則不用關注。

依賴收集的實現

Observable 和依賴收集已經是非常成熟的技術了,應該這個技術的框架有Vue 或者Mobx 之類,甚至iOS 最新的SwiftUI,Android 的Flutter 也是如此。這部分實現後續文章可能會專門展開來講,感興趣的可以展開閱讀下面兩個開源庫的實現:

簡化來看其大致實現原理是:

  • 使用Proxy 來追踪對變量的讀與寫
  • 組件渲染過程中發生讀取的的所有變量都會被記錄下來作為依賴
  • 某個變量發生修改的時候,會找到所有依賴自己的組件,觸發組件的重新渲染

blank

超越手寫代碼的性能

因為可視化的畫布編輯器天然隔絕了父子組件之間的props 傳遞,那麼雲鳳蝶將一個組件可能會更新的因素全部限制在屬性面板上發生的數據綁定範圍內,因此云鳳蝶可以選擇隔絕掉父子組件的級聯更新,讓整個頁面的更新性能變得極其精確。

blank

七、與服務端交互

傳統的ajax 請求代碼編寫細節太多,參數返回值訊息全靠文檔。而云鳳蝶的思路是從源頭出發,採用OpenAPI 的通用規範管理API 訊息,在螞蟻內部可以通過我們提供的工具自動從JAVA 代碼裡導出這份訊息並上傳到雲鳳蝶。

blank

一旦錄入OpenAPI 的接口訊息之後,開發者在頁面裡就可以一行代碼泛化調用HTTP API 了,而且附帶完整的編輯器類型提示和錯誤發現:

blank

基於這份訊息雲鳳蝶還可以幫助開發者做自動Mock,接口測試等等更豐富的功能。

八、結語:10 倍效能提升

10 倍效能提升代表著我們的美好願景,可視化搭建只是1,雲鳳蝶還將持續挖掘更多的0。

No Silver Bullet – Essence and Accident in Software Engineering這篇文章提到:
there is no single development, in either technology or management technique, which by itself promises even one order-of-magnitude improvement within a decade in producitivity, in reliability, in simplicity.
並不存在某種技術或管理手段,可以保證在10 年內給軟件開發的生產力,可靠性,簡單性上帶來量級的提升。

API 驅動

從CURD API 接口定義智能推導生成表格/表單/列表/詳情等頁面。

從數據格式描述智能推薦生成圖表。

blank

雲鳳蝶的可視化底盤實則擁有了一整套對Web 應用的描述DSL,不僅僅包含視圖組件樹,還包含數據綁定和交互邏輯,因此讓智能生成應用的難度太太降低,並且生成完的應用還可以被雲鳳蝶編輯器消費進行二次修改和迭代。

智能還原設計稿

  • Ant Design 規范特徵明顯,容易訓練
  • 還原成畫布元素後補充缺失交互即可上線

智能佈局

  • 擺出大概位置,一鍵格式化成符合Ant Design 設計規範的佈局

模型驅動

  • 只需製作業務模型,補充字段、邏輯
  • 一鍵完成DB、API 和UI 的全鏈路生成
  • Low-code ⽅方式書寫後端邏輯,Serverless 部署
  • 應⽤ Host,一鍵發布

相關資料


未來已來,時不我待!

雲鳳蝶招聘前端、Java、PD、設計崗位,未來等你共創!

如果你感興趣,歡迎聯繫[email protected][email protected]

What do you think?

Written by marketer

blank

螞蟻中後台快速研發平台的領域思考

blank

聲明式UI框架在類小程序運行的原理