lowcode-engine/docs/docs/specs/lowcode-spec.md

64 KiB
Raw Blame History

title, sidebar_position
title sidebar_position
《低代码引擎搭建协议规范》 0

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

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 协议结构

协议最顶层结构如下:

  • version { String } 当前协议版本号
  • componentsMap { Array } 组件映射关系
  • componentsTree { Array } 描述模版/页面/区块/低代码业务组件的组件树
  • utils { Array } 工具类扩展映射关系
  • i18n { Object } 国际化语料
  • constants { Object } 应用范围内的全局常量
  • css { string } 应用范围内的全局样式
  • config: { Object } 当前应用配置信息
  • meta: { Object } 当前应用元数据信息
  • dataSource: { Array } 当前应用的公共数据源

描述举例:

{
  "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"
          }
        }
      }]
    }]
  }],
  "constants": {
    "ENV": "prod",
    "DOMAIN": "xxx.com"
  },
  "css": "body {font-size: 12px;} .table { width: 100px;}",
  "config": {                                          // 当前应用配置信息
    "sdkVersion": "1.0.3",                             // 渲染模块版本
    "historyMode": "hash",                             // 浏览器路由browser  哈希路由hash
    "targetRootID": "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": {                                           // 应用元数据信息key 为业务自定义
    "name": "demo 应用",                               // 应用中文名称,
    "git_group": "appGroup",                          // 应用对应 git 分组名
    "project_name": "app_demo",                       // 应用对应 git 的 project 名称
    "description": "这是一个测试应用",                   // 应用描述
    "spma": "spa23d",                                 // 应用 spm A 位信息
    "creator": "月飞",
    "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"
    }
  }
}

2.1 协议版本号A

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

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

描述示例:

{
  "version": "1.0.0"
}

2.2 组件映射关系A

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

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

ComponentMap 结构描述如下:

参数 说明 类型 变量支持 默认值
componentName 协议中的组件名,唯一性,对应包导出的组件名,是一个有效的 JS 标识符,而且是大写字母打头 String - -
package npm 公域的 package name String - -
version package version String - -
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 组件 ID String - 系统属性
className 组件样式类名 String - 系统属性,支持变量表达式
style 组件内联样式 Object - 系统属性,单个内联样式属性值
ref 组件 ref 名称 String - 可通过 this.$(ref) 获取组件实例
extendProps 组件继承属性 变量 - 仅支持变量绑定,常用于继承属性对象
... 组件私有属性 - - -
2.3.1.2 css/less/scss 样式描述
参数 说明 类型 支持变量 默认值
css/less/scss 用于描述容器组件内部节点的样式,对应生成一个独立的样式文件,不支持 @import String - null

描述示例:

{
  "css": "body {font-size: 12px;} .table { width: 100px; }"
}
2.3.1.3 ComponentDataSource 对象描述
参数 说明 类型 支持变量 默认值 备注
list[] 数据源列表 ComponentDataSourceItem[] - - 成为为单个请求配置, 内容定义详见 ComponentDataSourceItem 对象描述
dataHandler 所有请求数据的处理函数 Function - - 详见 [dataHandler Function 描述](#2317-datahandler-function 描述)
2.3.1.4 ComponentDataSourceItem 对象描述
参数 说明 类型 支持变量 默认值 备注
id 数据请求 ID 标识 String - -
isInit 是否为初始数据 Boolean true 值为 true 时,将在组件初始化渲染时自动发送当前数据请求
isSync 是否需要串行执行 Boolean false 值为 true 时,当前请求将被串行执行
type 数据请求类型 String - fetch 支持四种类型fetch/mtop/jsonp/custom
shouldFetch 本次请求是否可以正常请求 (options: ComponentDataSourceItemOptions) => boolean - () => true function 参数参考 ComponentDataSourceItemOptions 对象描述
willFetch 单个数据结果请求参数处理函数 Function - options => options 只接受一个参数options返回值作为请求的 options当处理异常时使用原 options。也可以返回一个 Promiseresolve 的值作为请求的 optionsreject 时,使用原 options
requestHandler 自定义扩展的外部请求处理器 Function - - 仅 type='custom' 时生效
dataHandler request 成功后的回调函数 Function - response => response.data 参数:请求成功后 promise 的 value 值
errorHandler request 失败后的回调函数 Function - - 参数:请求出错 promise 的 error 内容
options {} 请求参数 ComponentDataSourceItemOptions - - 每种请求类型对应不同参数,详见

关于 dataHandler 于 errorHandler 的细节说明:

request 返回的是一个 promisedataHandler 和 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.dataerrorHandler 没有默认值
2.3.1.5 ComponentDataSourceItemOptions 对象描述
参数 说明 类型 支持变量 默认值 备注
uri 请求地址 String -
params 请求参数 Object {} 当前数据源默认请求参数(在运行时会被实际的 load 方法的参数替换,如果 load 的 params 没有则会使用当前 params)
method 请求方法 String GET
isCors 是否支持跨域 Boolean true 对应 credentials = 'include'
timeout 超时时长 Number 5000 单位 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 属性类型 String|Object - - 具体值内容结构参考《低代码引擎物料协议规范》内的“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 - {} 必填,详见
condition 渲染条件 Boolean true 选填,根据表达式结果判断是否渲染物料;支持变量表达式
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 低代码业务组件属性类型定义 ComponentPropDefinition[] - - 选填,仅用于定义低代码业务组件的属性数据类型。详见 ComponentPropDefinition 对象描述
condition 渲染条件 Boolean true 选填,根据表达式结果判断是否渲染物料;支持变量表达式
state 容器初始数据 Object - 选填,支持变量表达式
children 子组件 Array - 选填,支持变量表达式
css/less/scss 样式属性 String - 选填,详见 [css/less/scss 样式描述](#2312-csslessscss 样式描述)
lifeCycles 生命周期对象 ComponentLifeCycles - - 详见 ComponentLifeCycles 对象描述
methods 自定义方法对象 Object - - 选填,对象成员为函数类型
dataSource {} 数据源对象 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

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

ReactNode 描述:

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

举例描述:如 Card 的 title 属性

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

Function-Return-ReactNode 描述:

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

举例描述:如 Table.Columncell 属性

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

2.3.4.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.3.4.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": "this.getNum(this.state.num, this.state.num2) + '万'"
      }
    },
    "condition": {
      "type": "JSExpression",
      "value": "this.state.num > this.state.num2"
    }
  }]
}
2.3.4.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 三种容器实例的数据对象 state Object -
this.setState(newState, callback) 三种容器实例更新数据的方法 Function 这个 setState 通常会异步执行,详见下文 setState
this.customMethod() 三种容器实例的自定义方法 Function -
this.dataSourceMap {} 三种容器实例的数据源对象 Map Object 单个请求的 id 为 key, value 详见下文 DataSourceMapItem 结构描述
this.reloadDataSource() 三种容器实例的初始化异步数据请求重载 Function 返回 <Promise>
this.page {} 当前页面容器的实例对象 Class Instance
this.page.props 读取页面路由,参数等相关信息 Object query 查询参数 { key: value } 形式path 路径uri 页面唯一标识;其它扩展字段
this.page.xxx 继承 this 对象所有 API 此处 xxx 代指 this.page 中的其他 API
this.component {} 当前低代码业务组件容器的实例对象 Class Instance
this.component.props 读取低代码业务组件容器的外部传入的 props Object
this.component.xxx 继承 this 对象所有 API 此处 xxx 代指 this.component 中的其他 API
this.$(ref) 获取组件的引用(单个) Component Instance ref 对应组件上配置的 ref 属性,用于唯一标识一个组件;若有同名的,则会返回第一个匹配的。
this.$$(ref) 获取组件的引用(所有同名的) Array of Component Instances ref 对应组件上配置的 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 获取单个数据源上次请求状态 String loading、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 当前物料在循环体中的 index Number -

2.4 工具类扩展描述AA

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

参数 说明 类型 支持变量 默认值
utils[] 工具类扩展映射关系 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.5 国际化多语言支持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']"
    }
  }
}

2.6 应用范围内的全局常量AA

用于描述在整个应用内通用的全局常量,比如请求 API 的域名、环境等。

2.7 应用范围内的全局样式AA

用于描述在应用范围内的全局样式,比如 reset.css 等。

2.8 当前应用配置信息AA

用于描述当前应用的配置信息比如当前应用的路由模式、Shell/Layout、主题等。

注意:该字段为扩展字段,消费方式由各自场景自己决定,包括运行时和出码。

2.9 当前应用元数据信息AA

用于描述当前应用的元数据信息比如当前应用的名称、Git 信息、版本号等等。

注意:该字段为扩展字段,消费方式由各自场景自己决定,包括运行时和出码。

2.10 当前应用的公共数据源AA

用于描述当前应用的公共数据源,数据结构跟容器结构里的 ComponentDataSource 保持一致。 在运行时 / 出码使用时API 和应用级数据源 API 保持一致,都是 this.dataSourceMap['globalDSName'].load()

3 应用描述

3.1 文件目录

以下是推荐的应用目录结构,与标准源码 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.2 应用级别 APIs

下文中 xxx 代指任意 API

3.2.1 路由 Router API

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

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

  • this.utils.xxx

3.2.3 国际化相关 API

API 函数签名 说明
this.i18n (i18nKey: string, params?: { [paramName: string]: string; }) => string i18nKey 是语料的标识符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"
    }
  }
}