【低代码漫谈】 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 也就不远了。