【低代码漫谈】 lowcode-engine - Vue Renderer 尝试

6,971 阅读5分钟

导读

之前《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 源码

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,然后按照当前框架(React、Rax、Vue)的语法,实现 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 根本无法转换的可能。与其如此,笔者宁可选择不复用 adapter 提供的基础 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 也就不远了。