【低程式碼漫談】 lowcode-engine - Vue Renderer 嘗試

語言: CN / TW / HK

highlight: vs2015

導讀

之前《lowcode-engine 協議淺析》這篇文件分析了 lowcode-engine 的協議,有了一定的收穫。但是筆者發現官方只實現了 React 和 Rax 的方案,並沒有 Vue 的方案,而筆者周圍有很多 Vue 的專案,所以需要深入研究一下 lowcode-engine 的實現原理,甚至原始碼,然後嘗試實現一下 Vue Renderer。

正文

React Renderer Demo

官網給的 Demo 效果如下(原文有 bug,經過了修復):

image.png 可見,lowcode-react-renderer 實際上就是暴露出了一個 ReactRenderer 元件,它接收 schemacomponents 兩個屬性,然後就能在頁面當中渲染出結果了,僅此而已。

為了更能夠體現框架無關性,筆者又做了一個純 JS 語法的 Demo,如下:

image.png

雖然理論上知道 jsx 就是 JS,但是這樣一些心裡還是更踏實了點。既然是框架無關,那下一步就是實現 Vue Renderer 了,在此之前,我們先來看下 React Renderer 的具體實現。

React Renderer 原始碼

```ts import React, { Component, PureComponent, createElement, createContext, forwardRef, ReactInstance, ContextType } from 'react'; import ReactDOM from 'react-dom'; import { adapter, pageRendererFactory, componentRendererFactory, blockRendererFactory, addonRendererFactory, tempRendererFactory, rendererFactory, types, } from '@alilc/lowcode-renderer-core'; import ConfigProvider from '@alifd/next/lib/config-provider';

window.React = React; (window as any).ReactDom = ReactDOM;

adapter.setRuntime({ Component, PureComponent, createContext, createElement, forwardRef, findDOMNode: ReactDOM.findDOMNode, });

adapter.setRenderers({ PageRenderer: pageRendererFactory(), ComponentRenderer: componentRendererFactory(), BlockRenderer: blockRendererFactory(), AddonRenderer: addonRendererFactory(), TempRenderer: tempRendererFactory(), DivRenderer: blockRendererFactory(), });

adapter.setConfigProvider(ConfigProvider);

function factory(): types.IRenderComponent { const Renderer = rendererFactory(); return class ReactRenderer extends Renderer implements Component { readonly props: types.IRendererProps;

context: ContextType<any>;

setState: (
  state: types.IRendererState,
  callback?: () => void,
) => void;

forceUpdate: (callback?: () => void) => void;

refs: {
  [key: string]: ReactInstance;
};

constructor(props: types.IRendererProps, context: ContextType<any>) {
  super(props, context);
}

isValidComponent(obj: any) {
  return obj?.prototype?.isReactComponent || obj?.prototype instanceof Component;
}

}; }

export default factory(); ``` 沒錯,原始碼就這些,不到 70 行。Rax Renderer 的原始碼能稍微多一點,所有 5 個檔案加起來 200 多行。很驚訝,很神奇對嗎。這是怎麼做到的呢?這是不是意味著實現 Vue Renderer 也可以很簡單呢?

初看這部分程式碼就做了兩件事: 1. 用 adapter 注入了很多方法,主要分為 setRuntimesetRendererssetConfigProvider 3 大類; 2. 實現 factory 函式並 default 匯出。

所以很明顯了,下一步就是研究一下 adapterfactory 了。

Adapter & Factory

我們先來看一下渲染模組的架構圖:

image.png

更多內容見 渲染模組設計。結合架構圖和 React Renderer 的程式碼,我們基本已經捋清了: - setRuntime 需要注入的,是一些框架相關的最底層的基礎類或函式,也就是框架的基礎能力,比如 React 的 createElement、Component、PureComponent 等。其本質是框架暴露出的底層 API; - setRenderer 需要注入的,是用 runtime 基礎能力實現的 lowcode-engine 的基礎概念,如 PageRenderer、ComponentRenderer、BlockRenderer 等。其本質其實就是各種元件 Class,只不過是比較基礎的元件; - setConfigProvider 就是一個載入全域性配置的 Provider,一般是 UI 元件庫提供的能力; - factory 比較複雜,算是一個高階函式,返回值是一個可以 jsx 的 class。React Renderer 的原始碼我們可以看出,這個 class 繼承了 renderFactory() 返回的類,也繼承了 class Component,然後複寫了一些方法。其它都好理解,關鍵是這個 renderFactory 做了什麼。簡單說,就是利用剛才 adapter 注入的 API 實現渲染邏輯。再具體點,就是載入剛才注入 adapter 中的各種 API,然後按照當前框架的語法,實現 lowcode-engine 各種概念的渲染邏輯,當然還有對 schema 遞迴處理的能力。

撥開迷霧看本質,實際上適配層最重要的就是這個 renderFactory 內部邏輯的實現,其他僅僅是為其提供基礎 API 而已,可以看成是一些封裝好的基礎 utils。而 renderFactory 當中最主要的邏輯就是載入 schema 和 components 然後渲染。那麼怎麼實現 Vue Renderer 呢?

Vue Renderer

很遺憾,目前的結果沒那麼理想。adapter 的程式碼是按照 React 的習慣和邏輯實現的,筆者可以找到 React.createElementVue.h 是非常相似的,但是其他 API 相差實在是有點大。比如 context 的使用,React 中 createContext/Provider 和 Vue 中的 provide/inject 用法還是有很大差異的。所以實際上只有類 React 框架,比如 Rax 是可以比較方便的接入現有 adapter 層的。要想實現 Vue Renderer,理論上需要把 Vue 的底層 API 加工成 React 的 API,倒不是不可能,但是成本實在太大了,而且也不排除有些 API 根本無法轉換的可能。與其如此,筆者可能會選擇自己實現一個 renderFactory 函式,把解析 schema 和 components 的邏輯,用 Vue 的語法再實現一遍,也就是基本無法複用 @alilc/lowcode-renderer-core 提供的能力了。

而且!而且!而且!這裡還有一個更加重要的問題,那就是現有的視覺化編輯部分是用 React 實現的,這意味著即使實現了 Vue Renderer,你的元件也不能在編輯器的畫布中展示出來,官方也說了短期內不會支援 Vue 畫布。但是 schema + components => 渲染/出碼 這樣的功能還是可以跑通的。

結論

所以最後的結論是:目前要實現 Vue Render 成本非常大。尤其解決視覺化編輯器畫布渲染這塊,有相當大的工作量。

那麼這條路是不是就該放棄了呢?

至少對於筆者來說不是。《lowcode-engine 協議淺析》中也說了,筆者的目的是為了提高開發效率,目標使用者是研發,視覺化編輯並不是必須的功能。所以只要 schema + components => 渲染/出碼 這段功能可以較低成本實現就行。

另外,協議也就是 schema 的確是框架無關的,這個協議是可以 100% 複用的,這就已經是很大的一筆收穫了。最不濟,還可以通過現有視覺化編輯器輸出的 schema,來判斷其元件是如何設計和封裝的,其中最值得參考的就是表單和列表元件,這絕對是一次絕好的偷師機會。

所以接下來筆者計劃就是用 schema + Vue 成功封裝出一個表單元件和列表元件,相信做完這個,離真正的 Vue Render 也就不遠了。