低代码引擎搭建协议规范

358 阅读13分钟

# 《低代码引擎搭建协议规范》

# 1 介绍

# 1.1 本协议规范涉及的问题域

  • 定义本协议版本号规范
  • 定义本协议中每个子规范需要被支持的 Level
  • 定义本协议相关的领域名词
  • 定义搭建基础协议版本号规范(A)
  • 定义搭建基础协议组件映射关系规范(A)
  • 定义搭建基础协议组件树描述规范(A)
  • 定义搭建基础协议国际化多语言支持规范(AA)
  • 定义搭建基础协议无障碍访问规范(AAA)

# 1.2 协议草案起草人

  • 撰写:月飞、康为、林熠
  • 审阅:大果、潕量、九神、元彦、戊子、屹凡、金禅、前道、天晟、戊子、游鹿、光弘、力皓

# 1.3 版本号

1.0.0

# 1.4 协议版本号规范(A)

本协议采用语义版本号,版本号格式为 major.minor.patch 的形式。

  • major 是大版本号:用于发布不向下兼容的协议格式修改
  • minor 是小版本号:用于发布向下兼容的协议功能新增
  • patch 是补丁号:用于发布向下兼容的协议问题修正

# 1.5 协议中子规范 Level 定义

规范等级实现要求
A强制规范,必须实现;违反此类规范的协议描述数据将无法写入物料中心,不支持流通。
AA推荐规范,推荐实现;遵守此类规范有助于业务未来的扩展性和跨团队合作研发效率的提升。
AAA参考规范,根据业务场景实际诉求实现;是集团层面鼓励的技术实现引导。

# 1.6 名词术语

# 1.6.1 物料系统名词

  • 基础组件(Basic Component) :前端领域通用的基础组件,阿里巴巴前端委员会官方指定的基础组件库是 Fusion Next/AntD。

  • 图表组件(Chart Component) :前端领域通用的图表组件,有代表性的图表组件库有 BizCharts。

  • 业务组件(Business Component) :业务领域内基于基础组件之上定义的组件,可能会包含特定业务域的交互或者是业务数据,对外仅暴露可配置的属性,且必须发布到公域(如阿里 NPM);在同一个业务域内可以流通,但不需要确保可以跨业务域复用。

    • 低代码业务组件(Low-Code Business Component) :通过低代码编辑器搭建而来,有别于源码开发的业务组件,属于业务组件中的一种类型,遵循业务组件的定义;同时低代码业务组件还可以通过低代码编辑器继续多次编辑。
  • 布局组件(Layout Component) :前端领域通用的用于实现基础组件、图表组件、业务组件之间各类布局关系的组件,如三栏布局组件。

  • 区块(Block) :通过低代码搭建的方式,将一系列业务组件、布局组件进行嵌套组合而成,不对外提供可配置的属性。可通过 区块容器组的包裹,实现区块内部具备有完整的样式、事件、生命周期管理、状态管理、数据流转机制。能独立存在和运行,可通过复制 schema 实现跨页面、跨应用的快速复用,保障功能和数据的正常。

  • 页面(Page) :由组件 + 区块组合而成。由页面容器组件包裹,可描述页面级的状态管理和公共函数。

  • 模板(Template) :特定垂直业务领域内的业务组件、区块可组合为单个页面,或者是再配合路由组合为多个页面集,统称为模板。

# 1.6.2 低代码搭建系统名词

  • 搭建编辑器:使用可视化的方式实现页面搭建,支持组件 UI 编排、属性编辑、事件绑定、数据绑定,最终产出符合搭建基础协议规范的数据。

    • 属性面板:低代码编辑器内部用于组件、区块、页面的属性编辑、事件绑定、数据绑定的操作面板。
    • 画布面板:低代码编辑器内部用于 UI 编排的操作面板。
    • 大纲面板:低代码编辑器内部用于页面组件树展示的面板。
  • 编辑器框架:搭建编辑器的基础框架,包含主题配置机制、插件机制、setter 控件机制、快捷键管理、扩展点管理等底层基础设施。

  • 入料模块:专注于物料接入,能自动扫描、解析源码组件,并最终产出一份符合《低代码引擎物料协议规范》的 Schema JSON。

  • 编排模块:专注于 Schema 可视化编排,以可视化的交互方式提供页面结构编排服务,并最终产出一份符合《低代码搭建基础协议规范》的 Schema JSON。

  • 渲染模块:专注于将 Schema JSON 渲染为 UI 界面,最终呈现一个可交互的页面。

  • 出码模块 Schema2Code:专注于通过 Schema JSON 生成高质量源代码,将符合《低代码搭建基础协议规范》的 Schema JSON 数据分别转化为面向 React / Rax / 阿里小程序等终端可渲染的代码。

  • 事件绑定:是指为某个组件的某个事件绑定相关的事件处理动作,比如为某个组件的点击事件绑定一段处理函数响应动作(比如弹出对话框),每个组件可绑定的事件由该组件自行定义。

  • 数据绑定:是指为某个组件的某个属性绑定用于该属性使用的数据。

  • 生命周期: 一般指某个对象的生老病死,本文中指某个实体(组件、容器、区块等等)的创建、加载、显示、销毁等关键生命阶段的统称。

# 1.7 背景

  • 协议目标: 通过约束低代码引擎的搭建协议规范,让上层低代码编辑器的产出物(低代码业务组件、区块、应用)保持一致性,可跨低代码研发平台进行流通而提效,亦不阻碍集团业务间融合的发展。

  • 协议通

    • 协议顶层结构统一

      • 协议 schema 具备有完整的描述能力,包含版本、国际化、组件树、组件映射关系等;
      • 顶层属性 key、value 值的格式,必须保持一致;
    • 组件树描述统一

      • 源码组件描述;
      • 页面、区块、低代码业务组件这三种容器组件的描述;
      • 数据流描述,包含数据请求、数据状态管理、数据绑定描述;
      • 事件描述,包含统一事件上下文、统一搭建 API;
  • 物料通:指在相同领域内的不同搭建产品,可直接使用的物料。比如模版、区块、组件;

# 1.8 受众

本协议适用于所有使用低代码搭建平台来开发页面或组件的开发者,以及围绕此协议的相关工具或工程化方案的开发者。阅读及使用本协议,需要对低代码搭建平台的交互和实现有一定的了解,对前端开发相关技术栈的熟悉也会有帮助,协议中对通用的前端相关术语不会做进一步的解释说明。

# 1.9 使用范围

本协议描述的是低代码搭建平台产物(应用、页面、区块、组件)的 schema 结构,以及实现其数据状态更新(内置 api)、能力扩展、国际化等方面完整,只在低代码搭建场景下可用;

# 1.10 协议目标

一套面向开发者的 schema 规范,用于规范化约束搭建编辑器的输出,以及渲染模块和出码模块的输入,将搭建编辑器、渲染模块、出码模块解耦,保障搭建编辑器、渲染模块、出码模块的独立升级。

# 1.11 设计说明

  • 语义化:语义清晰,简明易懂,可读性强。
  • 渐进性描述:搭建的本质是通过 源码组件 进行嵌套组合,从小往大、依次组合生成 组件、区块、页面,最终通过云端构建生成 应用 的过程。因此在搭建基础协议中,我们需要知道如何去渐进性的描述组件、区块、页面、应用这 4 个实体概念。
  • 生成标准源码:明确每一个属性与源码对应的转换关系,可生成跟手写无差异的高质量标准源代码。
  • 可流通性:产物能在不同搭建产品中流通,不涉及任何私域数据存储。
  • 面向多端:不能仅面向 React,还有小程序等多端。
  • 支持国际化&无障碍访问标准的实现

# 2 协议结构

协议最顶层结构如下,包含5方面的描述内容:

  • version { String } 当前协议版本号
  • componentsMap { Array } 组件映射关系
  • componentsTree { Array } 描述模版/页面/区块/低代码业务组件的组件树
  • utils { Array } 工具类扩展映射关系
  • i18n { Object } 国际化语料

描述举例:

{
  "version": "1.0.0",                  // 当前协议版本号
  "componentsMap": [{                  // 组件描述
    "componentName": "Button",
    "package": "@alifd/next",
    "version": "1.0.0",
    "destructuring": true,
    "exportName": "Select",
    "subName": "Button"
  }],
  "utils": [{
    "name": "clone",
    "type": "npm",
    "content": {
      "package": "lodash",
      "version": "0.0.1",
      "exportName": "clone",
      "subName": "",
      "destructuring": false,
      "main": "/lib/clone"
    }
  }, {
    "name": "moment",
    "type": "npm",
    "content": {
      "package": "@alifd/next",
      "version": "0.0.1",
      "exportName": "Moment",
      "subName": "",
      "destructuring": true,
      "main": ""
    }
  }],
  "componentsTree": [{                 // 描述内容,值类型 Array
    "componentName": "Page",           // 单个页面,枚举类型 Page|Block|Component
    "fileName": "Page1",
    "props": {},
    "css": "body {font-size: 12px;} .table { width: 100px;}",
    "children": [{
      "componentName": "Div",
      "props": {
        "className": ""
      },
      "children": [{
        "componentName": "Button",
        "props": {
          "prop1": 1234,               // 简单 json 数据
          "prop2": [{                  // 简单 json 数据
            "label": "选项1",
            "value": 1
          }, {
            "label": "选项2",
            "value": 2
          }],
          "prop3": [{
            "name": "myName",
            "rule": {
              "type": "JSExpression",
              "value": "/\w+/i"
            }
          }],
          "valueBind": {               // 变量绑定
            "type": "JSExpression",
            "value": "this.state.user.name"
          },
          "onClick": {                 // 动作绑定
            "type": "JSFunction",
            "value": "function(e) { console.log(e.target.innerText) }"
          },
          "onClick2": {                // 动作绑定 2
            "type": "JSExpression",
            "value": "this.submit"
          }
        }
      }]
    }]
  }],
  "i18n": {
    "zh-CN": {
      "i18n-jwg27yo4": "你好",
      "i18n-jwg27yo3": "中国"
    },
    "en-US": {
      "i18n-jwg27yo4": "Hello",
      "i18n-jwg27yo3": "China"
    }
  }
}

# 2.1 协议版本号(A)

定义当前协议 schema 的版本号,不同的版本号对应不同的渲染 SDK,以保障不同版本搭建协议产物的正常渲染;

根属性名称类型说明变量支持默认值
versionString协议版本号-1.0.0

描述示例:

{
  "version": "1.0.0"
}

# 2.2 组件映射关系(A)

协议中用于描述 componentName 到公域组件映射关系的规范。

参数说明类型变量支持默认值
componentsMap[]描述组件映射关系的集合Array<ComponentMap>-null

ComponentMap 结构描述如下:

参数说明类型变量支持默认值
componentName协议中的组件名,唯一性,对应包导出的组件名,是一个有效的 JS 标识符,而且是大写字母打头String--
packagenpm 公域的 package nameString--
versionpackage versionString--
destructuring使用解构方式对模块进行导出Boolean--
exportName包导出的组件名String--
subName下标子组件名称String-
main包导出组件入口文件路径String--

描述示例:

{
  "componentsMap": [{
    "componentName": "Button",
    "package": "@alifd/next",
    "version": "1.0.0",
    "destructuring": true
  }, {
    "componentName": "MySelect",
    "package": "@alifd/next",
    "version": "1.0.0",
    "destructuring": true,
    "exportName": "Select"
  }, {
    "componentName": "ButtonGroup",
    "package": "@alifd/next",
    "version": "1.0.0",
    "destructuring": true,
    "exportName": "Button",
    "subName": "Group"
  }, {
    "componentName": "RadioGroup",
    "package": "@alifd/next",
    "version": "1.0.0",
    "destructuring": true,
    "exportName": "Radio",
    "subName": "Group"
  }, {
    "componentName": "CustomCard",
    "package": "@ali/custom-card",
    "version": "1.0.0"
  }, {
    "componentName": "CustomInput",
    "package": "@ali/custom",
    "version": "1.0.0",
    "main": "/lib/input",
    "destructuring": true,
    "exportName": "Input"
  }]
}

出码结果:

// 使用解构方式, destructuring is true.
import { Button } from '@alifd/next';

// 使用解构方式,且 exportName 和 componentName 不同
import { Select as MySelect } from '@alifd/next';

// 使用解构方式,并导出其子组件
import { Button } from '@alifd/next';
const ButtonGroup = Button.Group;

import { Radio } from '@alifd/next';
const RadioGroup = Radio.Group;

// 不使用解构方式进行导出
import CustomCard from '@ali/custom-card';

// 使用特定路径进行导出
import { Input as CustomInput } from '@ali/custom/lib/input';

# 2.3 组件树描述(A)

协议中用于描述搭建出来的组件树结构的规范,整个组件树的描述由组件结构&容器结构两种结构嵌套构成。

  • 组件结构:描述单个组件的名称、属性、子集的结构;
  • 容器结构:描述单个容器的数据、自定义方法、生命周期的结构,用于将完整页面进行模块化拆分。

与源码对应的转换关系如下:

  • 组件结构:转换成一个 .jsx 文件内 React Class 类 render 函数返回的 jsx 代码。
  • 容器结构:将转换成一个标准文件,如 React 的 jsx 文件, export 一个 React Class,包含生命周期定义、自定义方法、事件属性绑定、异步数据请求等。

# 2.3.1 基础结构描述 (A)

此部分定义了组件结构、容器结构的公共基础字段。

阅读时可先跳到后续章节,待需要时回来参考阅读

# 2.3.1.1 Props 结构描述

参数说明类型支持变量默认值备注
id组件 IDString-系统属性
className组件样式类名String-系统属性,支持变量表达式
style组件内联样式Object-系统属性,单个内联样式属性值
ref组件 ref 名称String-可通过 this.$(ref) 获取组件实例
extendProps组件继承属性变量-仅支持变量绑定,常用于继承属性对象
组件私有属性---

# 2.3.1.2 css/less/scss 样式描述

参数说明类型支持变量默认值
css/less/scss用于描述容器组件内部节点的样式,对应生成一个独立的样式文件,不支持 @importString-null

描述示例:

{
  "css": "body {font-size: 12px;} .table { width: 100px; }"
}

# 2.3.1.3 ComponentDataSource 对象描述

参数说明类型支持变量默认值备注
list[]数据源列表Array<ComponentDataSourceItem>--成为为单个请求配置, 内容定义详见 ComponentDataSourceItem 对象描述
dataHandler所有请求数据的处理函数Function--详见 [dataHandler Function 描述](#2317-datahandler-function 描述)

# 2.3.1.4 ComponentDataSourceItem 对象描述

参数说明类型支持变量默认值备注
id数据请求 ID 标识String--
isInit是否为初始数据Booleantrue值为 true 时,将在组件初始化渲染时自动发送当前数据请求
isSync是否需要串行执行Booleanfalse值为 true 时,当前请求将被串行执行
type数据请求类型String-fetch支持四种类型:fetch/mtop/jsonp/custom
shouldFetch本次请求是否可以正常请求(options: ComponentDataSourceItemOptions) => boolean-() => truefunction 参数参考 ComponentDataSourceItemOptions 对象描述
willFetch单个数据结果请求参数处理函数Function-options => options只接受一个参数(options),返回值作为请求的 options,当处理异常时,使用原 options。也可以返回一个 Promise,resolve 的值作为请求的 options,reject 时,使用原 options
requestHandler自定义扩展的外部请求处理器Function--仅 type=‘custom’ 时生效
dataHandlerrequest 成功后的回调函数Function-response => response.data参数: 请求成功后 promise 的 value 值
errorHandlerrequest 失败后的回调函数Function--参数: 请求出错 promise 的 error 内容
options {}请求参数ComponentDataSourceItemOptions--每种请求类型对应不同参数, 详见 ComponentDataSourceItemOptions 对象描述

关于 dataHandler 于 errorHandler 的细节说明:

request 返回的是一个 promise,dataHandler 和 errorHandler 遵循 Promise 对象的 then 方法,实际使用方式如下:

// 伪代码
try {
  const result = await request(fetchConfig).then(dataHandler, errorHandler);
  dataSourceItem.data = result;
  dataSourceItem.status = 'success';
} catch (err) {
  dataSourceItem.error = err;
  dataSourceItem.status = 'error';
}

注意:

  • dataHandler 和 errorHandler 只会走其中的一个回调
  • 它们都有修改 promise 状态的机会,意味着可以修改当前数据源最终状态
  • 最后返回的结果会被认为是当前数据源的最终结果,如果被 catch 了,那么会认为数据源请求出错
  • dataHandler 会有默认值,考虑到返回结果入参都是 response 完整对象,默认值会返回 response.data,errorHandler 没有默认值

# 2.3.1.5 ComponentDataSourceItemOptions 对象描述

参数说明类型支持变量默认值备注
uri请求地址String-
params请求参数Object{}当前数据源默认请求参数(在运行时会被实际的 load 方法的参数替换,如果 load 的 params 没有则会使用当前 params)
method请求方法StringGET
isCors是否支持跨域Booleantrue对应 credentials = 'include'
timeout超时时长Number5000单位 ms
headers请求头信息Object-自定义请求头

# 2.3.1.6 ComponentLifeCycles 对象描述

生命周期对象,schema 面向多端,不同 DSL 有不同的生命周期方法:

  • React:对于中后台 PC 物料,已明确使用 React 作为最终渲染框架,因此提案采用 React16 标准生命周期方法标准来定义生命周期方法,降低理解成本,支持生命周期如下:

    • constructor(props, context)

      • 说明:初始化渲染时执行,常用于设置 state 值。
    • render()

      • 说明:执行于容器组件 React Class 的 render 方法最前,常用于计算变量挂载到 this 对象上,供 props 上属性绑定。此 render() 方法不需要设置 return 返回值。
    • componentDidMount()

      • 说明:组件已加载
    • componentDidUpdate(prevProps, prevState, snapshot)

      • 说明:组件已更新
    • componentWillUnmount()

      • 说明:组件即将从 DOM 中移除
    • componentDidCatch(error, info)

      • 说明:组件捕获到异常
  • Rax:目前没有使用生命周期,使用 hooks 替代生命周期;

该对象由一系列 key-value 组成,key 为生命周期方法名,value 为 JSFunction 的描述,详见下方示例:

{
  "componentDidMount": {              // key 为上文中 React 的生命周期方法名
    "type": "JSFunction",             // type 目前仅支持 JSFunction
    "value": "function() {\           // value 为 javascript 函数
      console.log('did mount');\
    }"
  },
  "componentWillUnmount": {
    "type": "JSFunction",
    "value": "function() {\
      console.log('will unmount');\
    }"
  }
  ...
},

# 2.3.1.7 dataHandler Function 描述

  • 参数:为 dataMap 对象,包含字段如下:

    • key: 数据 id
    • value: 单个请求结果
  • 返回值:数据对象 data,将会在渲染引擎和 schemaToCode 中通过调用 this.setState(...) 将返回的数据对象生效到 state 中;支持返回一个 Promise,通过 resolve(返回数据),常用于串行发送请求场景。

# 2.3.1.8 ComponentPropDefinition 对象描述

参数说明类型支持变量默认值备注
name属性名称String--
propType属性类型StringObject--具体值内容结构,参考《阿里巴巴中后台前端物料规范》 内的 “2.2.2.3 组件属性信息”中描述的基本类型复合类型
description属性描述String-‘’
defaultValue属性默认值Any-undefined当 defaultValue 和 defaultProps 中存在同一个 prop 的默认值时,优先使用 defaultValue。

范例:

{
  "propDefinitions": [{
    "name": "title",
    "propType": "string",
    "defaultValue": "Default Title"
  }, {
    "name": "onClick",
    "propType": "func"
  }]
  ...
},

# 2.3.2 组件结构描述(A)

对应生成源码开发体系中 render 函数返回的 jsx 代码,主要描述有以下属性:

参数说明类型支持变量默认值备注
id组件唯一标识String-可选, 组件 id 由引擎随机生成(UUID),并保证唯一性,消费方为上层应用平台,在组件发生移动等场景需保持 id 不变
componentName组件名称String-Div必填,首字母大写, 同 [componentsMap](#22-组件映射关系 a) 中的要求
props {}组件属性对象Props-{}必填, 详见 Props 结构描述
condition渲染条件Booleantrue选填,根据表达式结果判断是否渲染物料;支持变量表达式
loop循环数据Array-选填,默认不进行循环渲染;支持变量表达式
loopArgs循环迭代对象、索引名称[String, String][“item”, “index”]选填,仅支持字符串
children子组件Array选填,支持变量表达式

描述举例:

{
  "componentName": "Button",
  "props": {
    "className": "btn",
    "style": {
      "width": 100,
      "height": 20
    },
    "text": "submit",
    "onClick": {
      "type": "JSFunction",
      "value": "function(e) {\
        console.log('btn click')\
      }"
    }
  },
  "condition": {
    "type": "JSExpression",
    "value": "!!this.state.isshow"
  },
  "loop": [],
  "loopArgs": ["item", "index"],
  "children": []
}

# 2.3.3 容器结构描述 (A)

容器是一类特殊的组件,在组件能力基础上增加了对生命周期对象、自定义方法、样式文件、数据源等信息的描述。包含低代码业务组件容器 Component区块容器 Block页面容器 Page 3 种。主要描述有以下属性:

  • 组件类型:componentName
  • 文件名称:fileName
  • 组件属性:props
  • state 状态管理:state
  • 生命周期 Hook 方法:lifeCycles
  • 自定义方法设置:methods
  • 异步数据源配置:dataSource
  • 条件渲染:condition
  • 样式文件:css/less/scss

详细描述:

参数说明类型支持变量默认值备注
componentName组件名称枚举类型,包括'Page' (代表页面容器)、'Block' (代表区块容器)、'Component' (代表低代码业务组件容器)-‘Div’必填,首字母大写
fileName文件名称String--必填,英文
props { }组件属性对象Props-{}必填,详见 Props 结构描述
static低代码业务组件类的静态对象
defaultProps低代码业务组件默认属性Object--选填,仅用于定义低代码业务组件的默认属性
propDefinitions低代码业务组件属性类型定义Array--选填,仅用于定义低代码业务组件的属性数据类型。详见 ComponentPropDefinition 对象描述
condition渲染条件Booleantrue选填,根据表达式结果判断是否渲染物料;支持变量表达式
state容器初始数据Object-选填,支持变量表达式
children子组件Array-选填,支持变量表达式
css/less/scss样式属性String-选填, 详见 [css/less/scss 样式描述](#2312-csslessscss 样式描述)
lifeCycles生命周期对象ComponentLifeCycles--详见 ComponentLifeCycles 对象描述
methods自定义方法对象Object--选填,对象成员为函数类型
dataSource {}数据源对象ComponentDataSource--选填,异步数据源, 详见 ComponentDataSource 对象描述

# 完整描述示例

描述示例 1:(正常 fetch/mtop/jsonp 请求):

{
  "componentName": "Block",
  "fileName": "block-1",
  "props": {
    "className": "luna-page",
    "style": {
      "background": "#dd2727"
    }
  },
  "children": [{
    "componentName": "Button",
    "props": {
      "text": {
        "type": "JSExpression",
        "value": "this.state.btnText"
      }
    }
  }],
  "state": {
    "btnText": "submit"
  },
  "css": "body {font-size: 12px;}",
  "lifeCycles": {
    "componentDidMount": {
      "type": "JSFunction",
      "value": "function() {\
        console.log('did mount');\
      }"
    },
    "componentWillUnmount": {
      "type": "JSFunction",
      "value": "function() {\
        console.log('will unmount');\
      }"
    }
  },
  "methods": {
    "testFunc": {
      "type": "JSFunction",
      "value": "function() {\
        console.log('test func');\
      }"
    }
  },
  "dataSource": {
    "list": [{
      "id": "list",
      "isInit": true,
      "type": "fetch/mtop/jsonp",
      "options": {
        "uri": "",
        "params": {},
        "method": "GET",
        "isCors": true,
        "timeout": 5000,
        "headers": {}
      },
      "dataHandler": {
        "type": "JSFunction",
        "value": "function(data, err) {}"
      }
    }],
    "dataHandler": {
      "type": "JSFunction",
      "value": "function(dataMap) { }"
    }
  },
  "condition": {
    "type": "JSExpression",
    "value": "!!this.state.isShow"
  }
}

描述示例 2:(自定义扩展请求处理器类型):

{
  "componentName": "Block",
  "fileName": "block-1",
  "props": {
    "className": "luna-page",
    "style": {
      "background": "#dd2727"
    }
  },
  ...
  "dataSource": {
    "list": [{
      "id": "list",
      "isInit": true,
      "type": "custom",
      "requestHandler": {
        "type": "JSFunction",
        "value": "this.utils.hsfHandler"
      },
      "options": {
        "uri": "hsf://xxx",
        "param1": "a",
        "param2": "b",
        ...
      },
      "dataHandler": {
        "type": "JSFunction",
        "value": "function(data, err) { }"
      }
    }],
    "dataHandler": {
      "type": "JSFunction",
      "value": "function(dataMap) { }"
    }
  }
}

# 2.3.4 属性值类型描述(A)

在上述组件结构容器结构描述中,每一个属性所对应的值,除了传统的 JS 值类型(String、Number、Object、Array、Boolean)外,还包含有节点类型事件函数类型变量类型等多种复杂类型;接下来将对于复杂类型的详细描述方式进行详细介绍。

# 2.3.4.1 节点类型(A)

通常用于描述组件的某一个属性为 ReactNode 或 Function-Return-ReactNode 的场景。该类属性的描述均以 JSSlot 的方式进行描述,详细描述如下:

ReactNode 描述:

参数说明值类型默认值备注
type值类型描述String‘JSSlot’固定值
value具体的值Arraynull内容为 NodeSchema 类型,详见组件结构描述

举例描述:如 Card 的 title 属性

{
  "componentName": "Card",
  "props": {
    "title": {
      "type": "JSSlot",
      "value": [{
        "componentName": "Icon",
        "props": {}
      },{
        "componentName": "Text",
        "props": {}
      }]
    },
    ...
  }
}

Function-Return-ReactNode 描述:

参数说明值类型默认值备注
type值类型描述String‘JSSlot’固定值
value具体的值Arraynull内容为 NodeSchema 类型,详见[组件结构描述](#232-组件结构描述 a)
params函数的参数Arraynull函数的入参,其子节点可以通过 this[参数名] 来获取对应的参数。

举例描述:如 Table.Column 的 cell 属性

{
  "componentName": "TabelColumn",
  "props": {
    "cell": {
      "type": "JSSlot",
      "params": ["value", "index", "record"],
      "value": [{
        "componentName": "Input",
        "props": {}
      }]
    },
    ...
  }
}

# 2.4.3.2 事件函数类型(A)

协议内的事件描述,主要包含容器结构生命周期自定义方法,以及组件结构事件函数类属性三类。所有事件函数的描述,均以 JSFunction 的方式进行描述,保留与原组件属性、生命周期(React / 小程序)一致的输入参数,并给所有事件函数 binding 统一一致的上下文(当前组件所在容器结构的 this 对象)。

事件函数类型的属性值描述如下:

{
  "type": "JSFunction",
  "value": "function onClick(){\
    console.log(123);\
  }"
}

描述举例:

{
  "componentName": "Block",
  "fileName": "block1",
  "props": {},
  "state": {
    "name": "lucy"
  },
  "lifeCycles": {
    "componentDidMount": {
      "type": "JSFunction",
      "value": "function() {\
        console.log('did mount');\
      }"
    },
    "componentWillUnmount": {
      "type": "JSFunction",
      "value": "function() {\
        console.log('will unmount');\
      }"
    }
  },
  "methods": {
    "getNum": {
      "type": "JSFunction",
      "value": "function() {\
        console.log('名称是:' + this.state.name)\
      }"
    }
  },
  "children": [{
    "componentName": "Button",
    "props": {
      "text": "按钮",
      "onClick": {
        "type": "JSFunction",
        "value": "function(e) {\
          console.log(e.target.innerText);\
        }"
      }
    }
  }]
}

# 2.4.3.3 变量类型(A)

在上述组件结构 或容器结构中,有多个属性的值类型是支持变量类型的,通常会通过变量形式来绑定某个数据,所有的变量表达式均通过 JSExpression 表达式,上下文与事件函数描述一致,表达式内通过 this 对象获取上下文;

变量类型的属性值描述如下:

  • return 数字类型

    {
      "type": "JSExpression",
      "value": "this.state.num"
    }
    
  • return 数字类型

    {
      "type": "JSExpression",
      "value": "this.state.num - this.state.num2"
    }
    
  • return “8万” 字符串类型

    {
      "type": "JSExpression",
      "value": "`${this.state.num}万`"
    }
    
  • return “8万” 字符串类型

    {
      "type": "JSExpression",
      "value": "this.state.num + '万'"
    }
    
  • return 13 数字类型

    {
      "type": "JSExpression",
      "value": "getNum(this.state.num, this.state.num2)"
    }
    
  • return true 布尔类型

    {
      "type": "JSExpression",
      "value": "this.state.num > this.state.num2"
    }
    

描述举例:

{
  "componentName": "Block",
  "fileName": "block1",
  "props": {},
  "state": {
    "num": 8,
    "num2": 5
  },
  "methods": {
    "getNum": {
      "type": "JSFunction",
      "value": "function(a, b){\
        return a + b;\
      }"
    }
  },
  "children": [{
    "componentName": "Button",
    "props": {
      "text": {
        "type": "JSExpression",
        "value": "getNum(this.state.num, this.state.num2) + '万'"
      }
    },
    "condition": {
      "type": "JSExpression",
      "value": "this.state.num > this.state.num2"
    }
  }]
}

# 2.4.3.4 国际化多语言类型(AA)

协议内的一些文本值内容,我们希望是和协议全局的国际化多语言语料是关联的,会按照全局国际化语言环境的不同使用对应的语料。所有国际化多语言值均以 i18n 结构描述。这样可以更为清晰且结构化得表达使用场景。

国际化多语言类型的属性值类型描述如下:

type Ti18n = {
  type: 'i18n';
  key: string; // i18n 结构中字段的 key 标识符
  params?: Record<string, JSDataType | JSExpression>; // 模版型 i18n 文案的入参,JSDataType 指代传统 JS 值类型
}

其中 key 对应协议 i18n 内容的语料键值,params 为语料为字符串模板时的变量内容。

假设协议已加入如下 i18n 内容:

{
  "i18n": {
    "zh-CN": {
      "i18n-jwg27yo4": "你好",
      "i18n-jwg27yo3": "${name}博士"
    },
    "en-US": {
      "i18n-jwg27yo4": "Hello",
      "i18n-jwg27yo3": "Doctor ${name}"
    }
  }
}

国际化多语言类型简单范例:

{
  "type": "i18n",
  "key": "i18n-jwg27yo4"
}

国际化多语言类型模板范例:

{
  "type": "i18n",
  "key": "i18n-jwg27yo3",
  "params": {
    "name": "Strange"
  }
}

描述举例:

{
  "componentName": "Button",
  "props": {
    "text": {
      "type": "i18n",
      "key": "i18n-jwg27yo4"
    }
  }
}

# 2.3.5 上下文 API 描述(A)

在上述事件类型描述变量类型描述中,在函数或 JS 表达式内,均可以通过 this 对象获取当前组件所在容器(React Class)的实例化对象,在搭建场景下的渲染模块和出码模块实现上,统一约定了该实例化 this 对象下所挂载的最小 API 集合,以保障搭建协议具备有一致的数据流事件上下文

# 2.3.5.1 容器 API:

参数说明类型备注
this {}当前区块容器的实例对象Class Instance-
this.state三种容器实例的数据对象 stateObject-
this.setState(newState, callback)三种容器实例更新数据的方法Function这个 setState 通常会异步执行,详见下文 setState
this.customMethod()三种容器实例的自定义方法Function-
this.dataSourceMap {}三种容器实例的数据源对象 MapObject单个请求的 id 为 key, value 详见下文 DataSourceMapItem 结构描述
this.reloadDataSource()三种容器实例的初始化异步数据请求重载Function返回
this.page {}当前页面容器的实例对象Class Instance
this.page.props读取页面路由,参数等相关信息Objectquery 查询参数 { key: value } 形式;path 路径;uri 页面唯一标识;其它扩展字段
this.page.xxx继承 this 对象所有 API此处 xxx 代指 this.page 中的其他 API
this.component {}当前低代码业务组件容器的实例对象Class Instance
this.component.props读取低代码业务组件容器的外部传入的 propsObject
this.component.xxx继承 this 对象所有 API此处 xxx 代指 this.component 中的其他 API
this.$(ref)获取组件的引用(单个)Component Instanceref 对应组件上配置的 ref 属性,用于唯一标识一个组件;若有同名的,则会返回第一个匹配的。
this.$$(ref)获取组件的引用(所有同名的)Array of Component Instancesref 对应组件上配置的 ref 属性,用于唯一标识一个组件;总是返回一个数组,里面是所有匹配 ref 的组件的引用。
# setState

setState() 将对容器 state 的更改排入队列,并通知低代码引擎需要使用更新后的 state 重新渲染此组件及其子组件。这是用于更新用户界面以响应事件处理器和处理服务器数据的主要方式。

请将 setState() 视为请求而不是立即更新组件的命令。为了更好的感知性能,低代码引擎会延迟调用它,然后通过一次传递更新多个组件。低代码引擎并不会保证 state 的变更会立即生效。

setState() 并不总是立即更新组件, 它会批量推迟更新。这使得在调用 setState() 后立即读取 this.state 成为了隐患。为了消除隐患,请使用 setState 的回调函数(setState(updater, callback)),callback 将在应用更新后触发。即,如下例所示:

this.setState(newState, () => {
  // 在这里更新已经生效了
  // 可以通过 this.state 拿到更新后的状态
  console.log(this.state);
});

// ⚠注意:这里拿到的并不是更新后的状态,这里还是之前的状态
console.log(this.state);

如需基于之前的 state 来设置当前的 state,则可以将传递一个 updater 函数:(state, props) => newState,例如:

this.setState((prevState) => ({ count: prevState.count + 1 }));

为了方便更新部分状态,setState 会将 newState 浅合并到新的 state 上。

# DataSourceMapItem 结构描述
参数说明类型备注
load(params)调用单个数据源Function当前参数 params 会替换 ComponentDataSourceItemOptions 对象描述中的 params 内容
status获取单个数据源上次请求状态Stringloading、loaded、error、init
data获取上次请求成功后的数据Any
error获取上次请求失败的错误对象Error 对象

备注: 如果组件没有在区块容器内,而是直接在页面内,那么 this === this.page

# 2.3.5.2 循环数据 API

获取在循环场景下的数据对象。举例:上层组件设置了 loop 循环数据,且设置了 loopArgs:["item", "index"],当前组件的属性表达式或绑定的事件函数中,可以通过 this 上下文获取所在循环的数据环境;默认值为 ['item','index'] ,如有多层循环,需要自定义不同 loopArgs,同样通过 this[自定义循环别名] 获取对应的循环数据和序号;

参数说明类型可选值
this.item获取当前 index 对应的循环体数据;Any-
this.index当前物料在循环体中的 indexNumber-

# 2.5 工具类扩展描述(AA)

用于描述物料开发过程中,自定义扩展或引入的第三方工具类(例如:lodash 及 moment),增强搭建基础协议的扩展性,提供通用的工具类方法的配置方案及调用 API。

参数说明类型支持变量默认值
utils[]工具类扩展映射关系Array<UtilItem>-
UtilItem.name工具类扩展项名称String-
UtilItem.type工具类扩展项类型枚举, 'npm' (代表公网 npm 类型) / 'tnpm' (代表阿里巴巴内部 npm 类型) / 'function' (代表 Javascript 函数类型)-
UtilItem.content工具类扩展项内容[ComponentMap 类型](#22-组件映射关系 a) 或 [JSFunction](#2432事件函数类型 a)-

描述示例:

{
  utils: [{
    name: 'clone',
    type: 'npm',
    content: {
      package: 'lodash',
      version: '0.0.1',
      exportName: 'clone',
      subName: '',
      destructuring: false,
      main: '/lib/clone'
    }
  }, {
    name: 'moment',
    type: 'npm',
    content: {
      package: '@alifd/next',
      version: '0.0.1',
      exportName: 'Moment',
      subName: '',
      destructuring: true,
      main: ''
    }
  }, {
    name: 'recordEvent',
    type: 'function',
    content: {
      type: 'JSFunction',
      value: "function(logkey, gmkey, gokey, reqMethod) {\n  goldlog.record('/xxx.event.' + logkey, gmkey, gokey, reqMethod);\n}"
    }
  }]
}

出码结果:

import clone from 'lodash/lib/clone';
import { Moment } from '@alifd/next';

export const recordEvent = function(logkey, gmkey, gokey, reqMethod) {
  goldlog.record('/xxx.event.' + logkey, gmkey, gokey, reqMethod);
}

...

扩展的工具类,用户可以通过统一的上下文 this.utils 方法获取所有扩展的工具类或自定义函数 ,例如:this.utils.moment、this.utils.clone。搭建协议中的使用方式如下所示:

{
  componentName: 'Div',
  props: {
    onClick: {
      type: 'JSFunction,
      value: 'function(){ this.utils.clone(this.state.data); }'
    }
  }
}

# 2.6 国际化多语言支持(AA)

协议中用于描述国际化语料和组件引用国际化语料的规范,遵循集团国际化中台关于国际化语料规范定义。

参数说明类型可选值默认值
i18n国际化语料信息Object-null

描述示例:

{
  "i18n": {
    "zh-CN": {
      "i18n-jwg27yo4": "你好",
      "i18n-jwg27yo3": "中国"
    },
    "en-US": {
      "i18n-jwg27yo4": "Hello",
      "i18n-jwg27yo3": "China"
    }
  }
}

使用举例:

{
  "componentName": "Button",
  "props": {
    "text": {
      "type": "i18n",
      "key": "i18n-jwg27yo4"
    }
  }
}
{
  "componentName": "Button",
  "props": {
    "text": "按钮",
    "onClick": {
      "type": "JSFunction",
      "value": "function() {\
        console.log(this.i18n('i18n-jwg27yo4'));\
      }"
    }
  }
}

使用举例(已废弃)

{
  "componentName": "Button",
  "props": {
    "text": {
      "type": "JSExpression",
      "value": "this.i18n['i18n-jwg27yo4']"
    }
  }
}

# 3 应用描述

面向开发者的,描述完整应用的 Schema 规范,用于规范化约束低代码平台完整应用输出,以及出码模块( Schema2Code) 或运行时动态渲染框架(预览)的输入

# 3.1 结构描述

  • version { String } 当前应用协议版本号
  • componentsMap { Array } 当前应用所有组件映射关系
  • componentsTree { Array } 描述应用所有页面、低代码组件的组件树
  • utils { Array } 应用范围内的全局自定义函数或第三方工具类扩展
  • css { string } 应用范围内的全局样式;
  • config: { Object } 当前应用配置信息
  • meta: { Object } 当前应用元数据信息
  • dataSource: { Array } 当前应用的公共数据源 (待定)
  • i18n { Object } 国际化语料

完整应用描述举例:

{
  "version": "1.0.0",                                         // 当前协议版本号
  "componentsMap": [{                                         // 依赖 npm 组件描述
    "componentName": "Button",
    "package": "alife/next",
    "version": "1.0.0",
    "destructuring": true,
    "exportName": "Select",
    "subName": "Button"
  }],
  "componentsTree": [{                                        // 应用内页面、低代码组件描述
    "componentName": "Page",                                  // 单个页面
    "fileName": "page_index",
    "props": {},
    "css": "body {font-size: 12px;} .table { width: 100px;}",
    "meta": {                                                 // 页面元信息
      "title": "首页",                                         // 页面标题描述
      "router": "/",                                          // 页面路由
      "spmb": "abef21",                                       // spm B 位
      "url": "https://fusion.design",          // 页面访问地址
      "creator": "xxx",
      "gmt_create": "2020-02-11 00:00:00",                    // 创建时间
      "gmt_modified": "2020-02-11 00:00:00",                  // 修改时间
      ...
    },
    "children": [{
      "componentName": "Div",
      "props": {
        "className": "red",
      },
      "children": [{
        "componentName": "Button",
        "props": {
          "type": "primary",
          "valueBind": {                                      // 变量绑定
            "type": "JSExpression",
            "value": "this.state.user.name"
          },
          "onClick": {                                        // 动作绑定
            "type": "JSExpression",
            "value": "function(e) { console.log(e.target.innerText) }",
          }
        },
      }]
    }, {
      "componentName": "Component",                           // 单个组件
      "fileName": "BasicLayout",                              // 组件名称
      "props": {},
      "css": "body {font-size: 12px;} .table { width: 100px;}",
      "meta": {                                               // 组件元信息
        "title": "导航组件",                                   // 组件中文标题
        "description": "这是一个导航类组件...",                  // 组件描述
        "creator": "xxx",
        "gmt_create": "2020-02-11 00:00:00",                  // 创建时间
        "gmt_modified": "2020-02-11 00:00:00",                // 修改时间
        ...
      },
      "children": [{
        "componentName": "Nav",
        "props": {
          "className": "red"
        },
        "children": [{
          "componentName": "NavItem",
          "props": {}
        }]
      }]
    }]
  }],
  "utils": [{
    "name": "clone",
    "type": "npm",
    "content": {
      "package": "lodash",
      "version": "0.0.1",
      "exportName": "clone",
      "subName": "",
      "destructuring": false,
      "main": "/lib/clone"
    }
  }, {
    "name": "beforeRequestHandler",
    "type": "function",
    "content": {
      "type": "JSFunction",
      "value": "function(){\n ... \n}"
    }
  }],
  "css": "body {font-size: 12px;} .table { width: 100px;}",
  "config": {                                                 // 当前应用配置信息
    "sdkVersion": "1.0.3",                                    // 渲染模块版本
    "historyMode": "hash",                                    // 浏览器路由:browser  哈希路由:hash
    "container": "J_Container",
    "layout": {
      "componentName": "BasicLayout",
      "props": {
        "logo": "...",
        "name": "测试网站"
      },
    },
    "theme": {
      // for Fusion use dpl defined
      "package": "@alife/theme-fusion",
      "version": "^0.1.0",
      // for Antd use variable
      "primary": "#ff9966"
    }
  },
  "meta": {                                                   // 应用元数据信息
    "name": "demo 应用",                                        // 应用中文名称,
    "git_group": "appGroup",                                  // 应用对应 git 分组名
    "project_name": "app_demo",                               // 应用对应 git 的 project 名称
    "description": "这是一个测试应用",                           // 应用描述
    "spma": "spa23d",                                         // 应用 spma A 位信息
    "gmt_create": "2020-02-11 00:00:00",                      // 创建时间
    "gmt_modified": "2020-02-11 00:00:00",                    // 修改时间
    ...
  },
  "i18n": {
    "zh-CN": {
      "i18n-jwg27yo4": "你好",
      "i18n-jwg27yo3": "中国"
    },
    "en-US": {
      "i18n-jwg27yo4": "Hello",
      "i18n-jwg27yo3": "China"
    }
  }
}

# 3.2 文件目录

以下是推荐的应用目录结构,与标准源码 build-scripts 对齐,这里的目录结构是帮助理解应用级协议的设计,不做强约束

├── META/                          # 低代码元数据信息,用于多分支冲突解决、数据回滚等功能
├── public/                        # 静态文件,构建时会 copy 到 build/ 目录
│   ├── index.html                 # 应用入口 HTML
│   └── favicon.png                # Favicon
├── src/
│   ├── components/                # 应用内的低代码业务组件
│   │   └── guide-component/
│   │       ├── index.js           # 组件入口
│   │       ├── components.js      # 组件依赖的其他组件
│   │       ├── schema.js          # schema 描述
│   │       └── index.scss         # css 样式
│   ├── pages/                     # 页面
│   │   └── home/                  # Home 页面
│   │       ├── index.js           # 页面入口
│   │       └── index.scss         # css 样式
│   ├── layouts/
│   │   └── basic-layout/          # layout 组件名称
│   │       ├── index.js           # layout 入口
│   │       ├── components.js      # layout 组件依赖的其他组件
│   │       ├── schema.js          # layout schema 描述
│   │       └── index.scss         # layout css 样式
│   ├── config/                    # 配置信息
│   │   ├── components.js          # 应用上下文所有组件
│   │   ├── routes.js              # 页面路由列表
│   │   └── app.js                 # 应用配置文件
│   ├── utils/                     # 工具库
│   │   └── index.js               # 应用第三方扩展函数
│   ├── locales/                   # [可选]国际化资源
│   │   ├── en-US
│   │   └── zh-CN
│   ├── global.scss                # 全局样式
│   └── index.jsx                  # 应用入口脚本, 依赖 config/routes.js 的路由配置动态生成路由;
├── webpack.config.js              # 项目工程配置,包含插件配置及自定义 webpack 配置等
├── README.md
├── package.json
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .stylelintignore
└── .stylelintrc.js

# 3.3 应用级别 APIs

下文中 xxx 代指任意 API

# 3.3.1 路由 Router API

  • this.location.xxx
  • this.history.xxx
  • this.match.xxx

# 3.3.2 应用级别的公共函数或第三方扩展

  • this.utils.xxx

# 3.3.3 国际化相关 API

API函数签名说明
this.i18n(i18nKey: string, params?: { [paramName: string]: string; }) => stringi18nKey 是语料的标识符,params 可选,是用来做模版字符串替换的。返回语料字符串
this.getLocale() => string返回当前环境语言 code
this.setLocale(locale: string) => void设置当前环境语言 code

使用范例:

{
  "componentsTree": [{
    "componentName": "Page",
    "fileName": "Page1",
    "props": {},
    "children": [{
      "componentName": "Div",
      "props": {},
      "children": [{
        "componentName": "Button",
        "props": {
          "children": {
            "type": "JSExpression",
            "value": "this.i18n('i18n-hello')"
          },
          "onClick": {
            "type": "JSFunction",
            "value": "function () { this.setLocale('en-US'); }"
          }
        },
      }, {
        "componentName": "Button",
        "props": {
          "children": {
            "type": "JSExpression",
            "value": "this.i18n('i18n-chicken', { count: this.state.count })"
          },
        },
      }]
    }],
  }],
  "i18n": {
    "zh-CN": {
      "i18n-hello": "你好",
      "i18n-chicken": "我有${count}只鸡"
    },
    "en-US": {
      "i18n-hello": "Hello",
      "i18n-chicken": "I have ${count} chicken"
    }
  }
}