5、架构:通用 Schema 设计

作为前端开发一定会非常熟悉 AST 抽象语法树(Abstract Syntax Tree),当浏览器加载 JS 代码时,它会首先将代码转换为一棵抽象语法树(AST),然后再根据 AST 来渲染对应的 DOM 结构,对于一款低代码产品来说,如果能直接去解析 AST 肯定是最方便但这也是麻烦的,因为 AST 包含的内容非常多,所以大部分的低代码产品都会使用自定义的 Schema 来描述搭建的内容。

但也由于 Schema 只是一种通用的协议,并没有非常好的规范与最佳实践,现阶段都是属于各自为战的边界探索阶段,所以各个低代码平台中的 Schema 的规范并不相同。

其实就算不是探索阶段,大多数平台的低代码产品肯定也很难做到统一,除了开发者的习惯也会涉及到用户习惯以及行业差异、产品定位等,此外商业产品为了盈利会主动营造技术壁垒、增加用户粘性、培养用户习惯以及迁移成本。

但当我们想把这个产品升级为 Pro Code 或者想再添加更多交互功能的时候,是不是等同于又重新创建了一个新 DSL,这也是我个人感觉低代码一个非常尴尬的点

当然在产品的初期由于时间与资源有限,肯定不会最开始就设计 DSL 解析,所以接下来我们将围绕 Schema 来逐步分析从设计落地以及扩展的全过程。

什么是 Schema 协议


Schema 本质上就是一个 JSON 格式的定义块,通过抽象属性定义来表达页面和组件的布局、属性配置、依赖关系、表达式解析,如果在偏向业务也有页面路由、多语种、数据源、权限等等各种各样的抽象声明。 因此,我们也将刚刚提到的内容统称为 Schema 协议,它也属于元数据结构模型的范畴。

如果想要进行更多的了解,可以 Google 看看元数据的相关内容。或者入群探讨,小册里就不再过多展开。

什么是协议渲染


当了解了 Schema 的基本概念后,接下来就需要具体来实施和设计相关 Schema 协议的实现了。

在正式开始设计协议之前,我们一起先来看下面的例子,一起来了解下协议渲染究竟是什么东西?

相信很多朋友为了提高效率或多或少都封装过一些通用型的组件,比如通过 JSON 配置来实现一组表单布局,如下图,是一个简单的表单区块:

前端实现的代码如下所示:

<Form>
  <Form.Item
    label="用户名"
    name="username"
    >
    <Input />
  </Form.Item>

  <Form.Item
    label="性别"
    name="sex"
    >
    <Select />
  </Form.Item>

  <Form.Item wrapperCol={{ offset: 8, span: 16 }}>
    <Button htmlType="reset">
      重置
    </Button>
    <Button type="primary" htmlType="submit">
      提交
    </Button>
  </Form.Item>
</Form>

可以看出,这其中大部分的代码都是冗余,特别是对于中台场景或者问答场景中会频繁大量的出现,所以社区早期就出现了配置式的解法,根据相对应的配置规则,我们可以将其抽象为以下代码:

import { Form } from 'antd'
import { FormRender, FormRenderProps } from '@you-team/form-render'

const config: FormRenderProps['config'] = [
  // username form.item config.
  {
    label: '用户名',
    name: 'username',
    renderType: 'Input',
  },
  // sex form.item config.
  {
    label: '性别',
    name: 'sex',
    renderType: 'Select',
  }
]

<FormRender 
  as={Form} 
  config={config} 
  onFinish={data => console.log('FormData', data)} 
/>

上述方式就是一个简单表单类型的 Schema 设计,借助封装好的 FormRender 组件来递归约定好的表单 Schema 协议快速进行页面表单内容的渲染。

此类方案是协议约定式渲染的方案之一,在低代码平台中,通常也是使用相同的方式来实现的,只不过会更加的通用,复杂程度也会更高。

目前而言,社区存在很多类似的实现解法,大部分都是 UI(config) 的思想。如果感兴趣可以搜索下对应的文章学习与了解。

设计与实现


上述的例子非常简单也是大家常用的组件封装方式,接下来就是我们这个低代码产品的 Schema 协议 的设计与实现了,首先来说下整个协议的主体采取 JSON 方式的原因:

  • 方便存储,可以存储到服务端中形成记录;
  • 方便操作,跨平台解析;
  • 结构简单,通俗易懂,方便开发者查阅。

如下图所示,Schema 协议第一个版本先预留了如下几个领域区块,分别是依赖管理国际化(多语种)状态管理数据源生命周期以及页面结构等耳熟能详的结构定义。

协议版本(version)

代表着当前协议的版本,用于后续协议 break change 带来的兼容问题,可以通过版本来区分渲染器和解析器。而版本的升降级也是有规范可循的,如社区中比较常见的像semver,大体上的规则如下:

  • major: 如果包含 Break Change(破坏更新)的内容;
  • minor: 当你产出了一个新的功能的时候(无破坏更新);
  • patch: 当你修复了一个 BUG 问题的时候(无破坏更新)。

依赖管理(library)

代表当前协议在编辑器中依赖的一些类库和包,为后续异步加载资源和动态引入留坑位。在内部声明出依赖的名称、加载的资源地址(如组件库会导出 index.js 和 index.css 多个资源),类库的声明名称等等。那么可以分析得出如下依赖的大体结构:

const librarys: SchemaModelConfig['librarys'] = [
  {
    name: 'dayjs',
    urls: [
      'https://unpkg.com/dayjs@1.11.7/dayjs.min.js'
    ],
    globalVar: 'dayjs'
  },
  {
    name: 'arco',
    urls: [
      'https://unpkg.com/@arco-design/web-react@2.46.3/dist/arco.min.js',
      'https://unpkg.com/@arco-design/web-react@latest/dist/arco-icon.min.js',
      'https://unpkg.com/@arco-design/web-react@2.46.3/dist/css/arco.css',
    ],
    globalVar: 'Arco'
  }
]

国际化(i18n)

管理当前协议生成页面的 react-i18n-next 相关的键对值,用于维护国际化项目时需要进行多语种的文案切换带来的业务诉求。参考业内成熟 i18n 的方案,多语种的协议字段就相对而言比较简单:


  const i18n: SchemaModelConfig['i18n'] = {
    zh: {},
    eu: {},
    ...后续补充需要支持的多语种
  },

状态管理(store)

维护一份页面上的状态。方便后续做绑定通信和事件广播的实现,用于赋予整个页面的组件联动交互,最常见的就是点击相关按钮唤起相关弹窗类型组件

当低代码产物为工程类型时,那么就会涉及到跨模块跨页面这种全局状态管理,当然随之而来的是这块的配置会更加复杂,包括 Schema 的设计与配置的形式。

数据源(dataSource)

在大多数业务场景当中,页面的元素结构渲染并不是根据静态数据来渲染的,而是通过获取相关接口中的远程数据来展示。所以数据源与远端挂钩,可以是远程的 JSON 文件,也可以是一个 fetch 请求,主要的目的是为了帮助页面组件支持动态渲染数据的能力。

一个请求包含以下几个重要的内容,请求资源 URIRequestHeaderResponse => params | query | body ,所以在定义数据源的时候,我们将其抽象成如下结构:

const i18n: SchemaModelConfig['dataSource'] = [
  {
    key: 'string|uuid',
    name: 'getUserList',
    request: {
      url: 'https://localhost:3000/user/list',
      params: {
        pageSize: 10,
        current: 1,
      },
      method: 'GET',
      body: {},
      header: {}
      ...AxiosInstanceConfig
    }
  }
]

// 最终会抽象成一个函数调用来动态的执行。
lowcodeSandBox?.loadDataSource('getUserList', ...其他参数): Promise<any>

生命周期(lifeCycles)

一个项目的使用中有初始化使用中销毁等多个不同的生命周期,每个状态需要做的事情也不同,比如在程序初始化时会加载或者配置后续使用中需要的数据、资源等,同理对于低代码平台应用而言,搭建页面时与传统项目一样,同样需要自定义一套生命周期来帮助更好管理产物的拉取、Dom 渲染、数据更新等操作。

页面结构(htmlBody)

与 虚拟 DOM 相似,本质上是对于当前页面渲染的抽象结构,便于跨平台之间的转换,为后续运行时渲染和动态出码垫定基础,提供后续结构化转换的能力。

我们先来看一下 React 组件的createElement方法的构成:

React.createElement(type, props, children);
  • type: 可以是原生标签,也可以是 函数组件 和 Class 组件 等;
  • props:组件元素需要的属性;
  • chidren:组件内容;

熟悉 React 的同学都知道,在编译时我们所写的 JSX|TSX 会被编辑成 React.createElement 执行函数,而我们抽象出来的 Schema 结构也是做类似的事情。

如下代码所示,就是一个对页面的抽象设计,其中主要包含的内容就是渲染的组件名称Props子组件等等信息。

至于属性具体有什么作用,在后续相关的实战章节会着重的分析,在这里只要先了解页面结构的基本画像即可。

{
  "ROOT": {
    "type": {
      "resolvedName": "Container"
    },
    "props": {
      "width": 800,
      "height": "100%",
      "paddingTop": 20,
      "paddingBottom": 20,
      "paddingLeft": 20,
      "paddingRight": 20,
      "background": "#FFFFFF"
    },
    "displayName": "基础容器",
    "custom": {},
    "hidden": false,
    "nodes": [
      "rpVYvatknx"
    ],
    "linkedNodes": {}
  },
  "rpVYvatknx": {
    "type": {
      "resolvedName": "Text"
    },
    "props": {},
    "displayName": "文本",
    "custom": {},
    "parent": "ROOT",
    "hidden": false,
    "nodes": [],
    "linkedNodes": {}
  }
}

最后

以上就是根据思维导图中初步拟定的协议草稿的字段定义解释。

在有了明确的定义结构后,我们就可以写出一个简单的Schema的数据结构,如下所示:

const schema = JSON.stringify({
  version: 1.0.0,
  librarys: [],
  i18n: {
    zh: {},
    eu: {},
  },
  store: {},
  dataSource: {},
  lifeCycles: {},
  htmlBody: {
  "ROOT": {
    "type": {
      "resolvedName": "Container"
    },
    "props": {
      "width": 800,
      "height": "100%",
      "paddingTop": 20,
      "paddingBottom": 20,
      "paddingLeft": 20,
      "paddingRight": 20,
      "background": "#FFFFFF"
    },
    "displayName": "基础容器",
    "custom": {},
    "hidden": false,
    "nodes": [
      "rpVYvatknx"
    ],
    "linkedNodes": {}
  },
  "rpVYvatknx": {
    "type": {
      "resolvedName": "Text"
    },
    "props": {},
    "displayName": "文本",
    "custom": {},
    "parent": "ROOT",
    "hidden": false,
    "nodes": [],
    "linkedNodes": {}
  }
}
})

这里需要注意的是,你的协议一定要遵守 JSON 数据格式的约束,否则会导致解析时出现问题,为了避免开发中出现解析 Schema 产生不可预期的错误,可以使用第三方推荐的 JSON Schema 库来检验 Schema 是否符合规范:

  • json-schema-validator
  • json-schema

总结

作为低代码编辑器的通用能力之一,Schema 协议在设计到使用中起着至关重要的转换器作用,使得不同的编辑器和工具之间可以共享和使用相同的数据结构,方便地将数据在不同的应用程序和系统之间进行转换和交换,从而实现更高效、更可靠的工作流程。

目前初版协议起草其实已经能够面对绝大部分的问题了,在后续实战中涉及更加复杂的功能需要依赖协议的话,则可以在此基础上继续延伸做扩展。

此章节的内容会随着项目的更新进度不断优化

写在最后

如果你有什么疑问或者更好的建议,欢迎在评论区提出。 👏

5 架构:通用 Schema 设计

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/35746.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[工业互联-16]:工业Windows操作系统与实时性方案

目录 第1章 Windows操作系统 1.1 简介 1.2 Windows架构 第2章 工业Windows操作系统 2.1 简介 2.2 常见的工业Windows操作系统版本 2.3 定制化工业Windows 第3章 EtherCAT实时Windows方案 3.1 实时Windows的缘由 3.2 总体框架 3.3 ROS2方案 3.4 方案1&#xff1a;使…

Sumifs函数(excel)

SUMIFS 函数是一个数学与三角函数&#xff0c;用于计算其满足多个条件的全部参数的总量。excel如何使用Sumifs函数&#xff1f; 工具/原料 联想ThinkPad X1 windows7 WPS office2021 方法/步骤 首先运行office软件&#xff0c;打开一份表格&#xff0c;今天我们要计算以“…

ASL-QPSO|改进量子粒子群自适应算法及其实现(Matlab)

作者在前面的文章中介绍了量子粒子群算法&#xff0c;量子粒子群算法不但继承粒子群算法的优点&#xff0c;还有它自身计算模型更加简洁&#xff0c;控制参数更少等更加突出的优势&#xff0c;但依然存在着一定的局限性。 例如也会存在着早熟收敛的问题&#xff0c;随着迭代次数…

Android oss policy上传

OSS Policy方式上传 一、 流程对比1.1 普通上传1.2 服务端签名后直传 二、获取上传的policy签名配置三、请求OSS上传文件四、调用应用服务器接口同步文件五、关于上传OSS报错注意事项六、附送链接 一、 流程对比 1.1 普通上传 缺点&#xff1a; 上传慢&#xff1a;用户数据需…

OpenCloudOS社区开源,助力软件开发

早前红帽宣布限制源代码访问性的政策&#xff0c;并解释说RHEL相关源码仅通过CentOS Stream公开&#xff0c;付费客户和合作伙伴可通过Red Hat Customer Portal访问到源代码&#xff0c;由此也导致非客户获取源码越来越麻烦&#xff0c; 据了解&#xff0c;CentOS是红帽发行的…

设计模式之责任链模式

文章目录 1、基本介绍2、包含角色3、场景推导4、责任链模式的优缺点5、使用场景 1、基本介绍 王二狗本来是干Android开发的&#xff0c;最近公司想让他把IOS的活也干了&#xff0c;但是Windows笔记本不能开发IOS&#xff0c;所以二狗提出买一台Mac笔记本电脑。这花钱的事需要领…

用android studio 测试发行包

在google play 发行app&#xff0c;需要用bundle&#xff0c;而不是apk。 bundle 比apk 要小很多&#xff0c;比如我的app-release.aab 29,736 KB&#xff0c; 而app-release.apk 是62,305KB。这就是少了一半多。但是apk 直接复制就可以安装&#xff0c;bundle 需要上传google…

前置微小信号放大器怎么用

前置微小信号放大器是一种用于将微弱信号从传感器转换成足够强度的信号以便更好地进行检测和处理的设备。它主要应用于各种传感器领域&#xff0c;例如温度传感器、压力传感器、光学传感器和生物传感器等。前置微小信号放大器的作用是提高信号的信噪比&#xff0c;减小噪声干扰…

Java类和对象

文章目录 什么是面向对象类的语法类的实例化 类和对象的关系this引用this引用的特性类的构造方法构造方法的特性 对象初始化的流程封装访问限定符包的概念常见的包 Staticstatic修饰成员变量static修饰成员方法 代码块的概念内部类内部类的特性 什么是面向对象 面向对象是解决…

Spring Cloud Alibaba 之 Nacos精讲

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

二次-InsCode Stable Diffusion 美图活动一期

模型&#xff1a; AbyssOrangeMix2 - SFW_Soft NSFW_AbyssOrangeMix2_sfw.safetensors 参数配置&#xff1a; 正&#xff1a;Mountains and seas, people 负&#xff1a;NSFW, (worst quality:2), (low quality:2), (normal quality:2), lowres, normal quality, ((monochr…

手把手教大家实现 npm 包,并发布 npm 仓库,搭建文档(二)

昨天写了第一篇文章,反响平平 一个 NPM 包,帮助数十万程序员提高数十倍效率,难道不开源出来?(一) 首先贴下我们的官网 【预览页】 https://kennana.github.io/toolkit-use/ 我们的推特 【toolkituse】 https://twitter.com/Toolkituse 我们的github 【toolkit-u…

【人工智能与机器学习】基于卷积神经网络CNN的猫狗识别

文章目录 1 引言2 卷积神经网络概述2.1 卷积神经网络的背景介绍2.2 CNN的网络结构2.2.1 卷积层2.2.2 激活函数2.2.3 池化层2.2.4 全连接层 2.3 CNN的训练过程图解2.4 CNN的基本特征2.4.1 局部感知&#xff08;Local Connectivity&#xff09;2.4.2 参数共享(Parameter Sharing)…

Django_类视图(五)

目录 类视图优点 使用方法 定义类视图 添加类视图路由 类视图原理 类视图的二次封装 类视图二次封装代码如下 编写视图 配置路由 访问url结果 源码等资料获取方法 类视图优点 使用django的函数视图&#xff0c;如果要让同一个视图实现不同的请求方式实现不同的逻辑…

unity3d 入门1

新建一个3D core项目&#xff1b; 自动新建一个示例场景&#xff0c;仅包含2个对象&#xff0c;一个主摄像机&#xff0c;一个方向光&#xff1b;在Hierarchy层次视图中看到如下&#xff1b;场景使用一个小立方体来表示&#xff0c;下面的对象也使用一个小立方体 表示&#xf…

dedecms后台数据库备份迁移流程

dedecms网站正常使用需要两部分,网站文件和数据库.两者缺一不可. dedecms上传网站文件后,还要导入数据库,如果您只有网站后台备份,没有其他格式sql备份文件,请按照下面流程重装dedecms,并操作恢复数据库 . 需要选确定/wwwroot/data/backupdata/下是否有对应备份 如不存在备份…

使用TypeScript实现贪吃蛇小游戏(网页版)

本项目使用webpackts所编写 下边是项目的文件目录 /src下边的index.html页面是入口文件 index.ts是引入所有的ts文件 /modules文件夹是用来存放所有类的 index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"…

FreeRTOS 低功耗模式设计 STM32平台

1. STM32F105RBT6 的三种低功耗模式 1.1 sleep睡眠模式、stop停机模式、standby 待机模式 1.2 STM32中文参考手册有介绍STM32 低功耗模式的介绍 2. FreeRTOS 采用的是时间片轮转的抢占式任务调度机制&#xff0c;其低功耗设计思路一般是&#xff1a; ① 当运行空闲任务&#…

第三章 SSD存储介质:闪存 3.1

3.1 闪存物理结构 闪存芯片从小到大依此是由&#xff1a;cell&#xff08;单元&#xff09;、page&#xff08;页&#xff09;、block&#xff08;块&#xff09;、plane&#xff08;平面&#xff09;、die&#xff08;核心&#xff09;、NAND flash&#xff08;闪存芯片&#…

C/C++指针从0到99(详解)

目录 一&#xff0c;指针的基础理解 二&#xff0c;指针的基本使用 三&#xff0c;为什么要用指针 四&#xff0c;指针与数组的联系 五&#xff0c;指针的拓展使用 1&#xff09;指针数组 2)数组指针 3&#xff09;函数指针 结构&#xff1a;返回类型 &#xff08;*p)…