咱们目前采用的是antd + react(umi)的框架作业务开发。在业务开发过程当中会有较多频繁出现而且类似度很高的场景,好比基于一个table的基础的增删改查,这个相信你们都很是熟悉。在接到一个新的业务需求的时候,相信有很多人会选择copy一份功能相似的代码而后基于这份代码去改造以知足当前业务,固然我目前也是这样作的~html
其实想把这块功能提取成一个公共组建的想法由来已久,最近开始作基础组件,便拿这个下手了。通过一周左右的时间完成了基础组件的编写。react
查看基础支持的 功能点API。
基本的思路是经过json生成一些抽象配置,而后经过解析json的抽象配置+渲染器最终生成页面。json配置涵盖了目前80%的业务场景的基本需求,可是可扩展性很低。好比一些复杂的业务场景:表单的关联校验
、数据关联显示
、多级列表下钻
等等功能。虽然经过一些较为复杂的处理能够把这些功能融入进来,但最终组件将会异常庞大难以维护。git
因此,我能不能经过这些json配置经过某种工具生成对应的代码?这样一来以上提到的问题就彻底不存在了,由于这和咱们本身写的代码彻底同样,工具只是帮咱们完成初始化的过程。因此后来想了不少办法,最初采用template string
的方式,这种方式较为简单粗暴,无非经过string中嵌套变量的判断来输出code。可是在实际写的时候发现不少问题,好比github
function
的输出(JSON.stringify会将function忽略)最终学习了一些生成代码的工具好比angular-cli以及一些关于js生成代码的文章,主要是经过知乎上的这篇讨论了解到了你们是怎么处理这种问题的。最终决定采用babel的生态链来解决上述遇到的问题。express
咱们目前采用的方式是基于antd+react(umi)编写通用的CRUD模板,而后经过代码生成器解析json中的配置生成对应的代码,大体的流程是:json
React --> JavaScript AST ---> Code Generator --> Compiler --> Page后端
目前功能只是完成了初步版本,待应用在项目中使用一段时间稳定以后将会开源~
Babel是一个工具链,主要用于编译ECMAScript 2015+代码转换为向后兼容的可运行在各类浏览器上的JavaScript。主要功能:api
Understanding ASTs by Building Your Own Babel Plugin数组
如上提供了babel基本的流程及一篇介绍AST的文章。浏览器
个人理解中好比一段string类型code,首先经过babel.transform会将code转为一个包含AST(Abstract Syntax Tree)的Object,一样可使用@babel/generator将AST转为code完成逆向过程。
例如一段变量声明代码:
const a = 1;
在解析以后的结构为:
{ "type": "Program", "start": 0, "end": 191, "body": [ { "type": "VariableDeclaration", "start": 179, "end": 191, "declarations": [ { "type": "VariableDeclarator", "start": 185, "end": 190, "id": { "type": "Identifier", "start": 185, "end": 186, "name": "a" }, "init": { "type": "Literal", "start": 189, "end": 190, "value": 1, "raw": "1" } } ], "kind": "const" } ], "sourceType": "module" }
首先类型为VariableDeclaration
,首先他的类型是const
,能够经过点击查看api其它还有let
、var
的值。其次是声明declarations
部分,这里值为数组,由于咱们能够同时定义多个变量。数组中值的类型为VariableDeclarator
,包含id
和init
两个参数,分别为变量名称以及变量值。id
的类型为Identifier
,译为修饰符便是变量名称。init
类型为Literal
,便是常量,通常经常使用的有stringLiteral
、numericliteral
、booleanliteral
等。此时即完成了变量赋值的过程。
固然这只是很简单的语法转换,若是你们想学习更多关于转换及类型的知识,可参考以下两个官方连接:
首先定义目录结构:
. ├── genCode // 代码生成器 | ├── genDetail // 须要新页面打开时单独的detail目录 | └── genIndex // 首页 | └── genModels // umi models | └── genServices // umi services | └── genTableFilter // table筛选区域 | └── genTableForm // 非新页面模式,新增/更新模态框 | └── genUpsert // 新页面模式下,新增/更新页面 | └── genUtils // 生成工具类 ├── schema // 模型定义文件 | ├── table // 当前要生成的模型 | └── ├──config.js // 基础配置 | └── └──dataSchema.js // 列表、新增、更新配置 | └── └──querySchema.js // 筛选项配置 ├── scripts // 生成脚本 | ├── generateCode.js // 生成主文件 | └── index.js // 入口 | └── utils.js // 工具类 ├── toCopyFiles // 生成时须要拷贝的文件,好比less └── index.js // 主入口
主体流程为:
eslint ${filePath} --fix
格式化生成的代码。其中主要模块为genCode文件夹中根据json配置生成代码的过程。
以genModels为例,首先提取可使用template string
完成的部分,减小代码解析的工做量。
module.exports = (tableConfig) => { return ` import { message } from 'antd'; import { routerRedux } from 'dva/router' import { parse } from 'qs' ${dynamicImport(dicArray, namespace)} export default { namespace: '${namespace}', state: { ... }, effects: { *fetch({ payload }, { call, put }) { const response = yield call(queryData, payload); if (response && response.errorCode === 0) { yield put({ type: 'save', payload: response.data, }); } else { message.error(response && response.errorMessage || '请求失败') } }, ..., ${dynamicYieldFunction(dicArray)} }, reducers: { save(state, action) { return { ...state, data: action.payload, }; }, ..., ${dynamicReducerFunction(dicArray)} }, }; ` }
由于列表数据可能有字典项从后台获取值来对应显示,因此import
、effects
、reducers
模块均有需根据配置动态生成的代码。
以dynamecImport为例:
function dynamicImport (dicArray, namespace) { // 基础api import let baseImport = [ 'queryData', 'removeData', 'addData', 'updateData', 'findById' ] // 判断json数据中是否有需从后台加载项 if (dicArray && dicArray.length) { baseImport = baseImport.concat(dicArray.map(key => getInjectVariableKey(key))) } // 遍历生成依赖项 const _importDeclarationArray = map(specifier => ( _importDeclarationArray.push(t.importSpecifier(t.identifier(specifier), t.identifier(specifier))) )) // 定义importDeclaration const ast = t.importDeclaration( _importDeclarationArray, t.stringLiteral(`../services/${namespace}`) ) // 经过@babel/generator 将ast生成code const { code } = generate(ast) return code }
其它代码生成逻辑相似,有不肯定如何生成的部分可参考上方提供的连接完成代码转换再去生成。
如有经过babel转换没法生成的代码,可经过正则来完成。
例如如下umi-models
代码:
*__dicData({ payload }, { call, put }) { const response = yield call(__dicData, payload); if (response && response.errorCode === 0) { yield put({ type: 'updateDic', payload: response.data, }); } else { message.error(response && response.errorMessage || '请求失败') } }
基础代码可经过yieldExpression
生成,可是转换以后无function以后的*
符号,反复差了文档以后没有解决办法,最后只能将生成完的code利用正则替换来解决。
若是你们有遇到相似的问题欢迎讨论~
useEffect(() => { props.form.setFieldsValue({ editorArea: BraftEditor.createEditorState(current.editorArea), editorArea2: BraftEditor.createEditorState(current.editorArea2) }); }, [current.editorArea, current.editorArea2]);
参数规范参考react-antd-admin
功能配置包含三个基础配置文件:
config.json
配置基本属性dataSchema.json
配置列表及新增修改字段querySchema.json
配置筛选区域字段参数 | 必填 | 类型 | 默认值 | 说明 |
---|---|---|---|---|
namespace | true | string | null | 命名空间 |
showExport | false | boolean | true | 是否显示导出 |
showCreate | false | boolean | true | 是否显示建立 |
showDetail | false | boolean | true | 是否显示查看 |
showUpdate | false | boolean | true | 是否显示修改 |
showDelete | false | boolean | true | 是否显示删除 |
newRouterMode | false | boolean | false | 在新的页面新增/编辑/查看详情。若包含富文本编辑器,建议此值设为true,富文本在模态框展现不是很是美观。 |
showBatchDelete | false | boolean | true | 是否显示批量删除,需multiSelection为 true |
multiSelection | false | boolean | true | 是否支持多选 |
defaultDateFormat | false | string | 'YYYY-MM-DD' | 日期格式 |
upload | false | object | null | 上传相关配置,上传图片和上传普通文件分别配置。 详见下方upload属性 |
pagination | false | object | null | 分页相关配置, 详见下方pagination属性 |
dictionary | false | array | null | 须要请求的字典项,用于下拉框或treeSelect的值为从后端获取的状况,可在dataSchema 和querySchema中使用, 详见下方dictionary属性 |
参数 | 必填 | 类型 | 默认值 | 说明 |
---|---|---|---|---|
uploadUrl | false | string | null | 默认的上传接口.优先级image/fileApiUrl > uploadUrl > Global.apiPath |
imageApiUrl | false | string | null | 默认的图片上传接口 |
fileApiUrl | false | string | null | 默认的文件上传接口 |
image | false | string | '/uploadImage' | 默认的上传图片接口 |
imageSizeLimit | false | number | 1500 | 默认的图片大小限制, 单位KB |
file | false | string | '/uploadFile' | 默认的上传文件接口 |
fileSizeLimit | false | number | 10240 | 默认的文件大小限制, 单位KB |
参数 | 必填 | 类型 | 默认值 | 说明 |
---|---|---|---|---|
pageSize | false | number | 10 | 每页显示数量 |
showSizeChanger | false | boolean | false | 是否能够改变pageSize |
pageSizeOptions | false | array | ['10', '20', '50', '100'] | 指定每页能够显示多少条 |
showQuickJumper | false | boolean | false | 是否能够快速跳转至某页 |
showTotal | false | boolean | true | 是否显示总数 |
参数 | 必填 | 类型 | 默认值 | 说明 |
---|---|---|---|---|
key | true | string | null | 变量标识 |
url | true | string | null | 请求数据地址 |
参数 | 必填 | 类型 | 默认值 | 说明 |
---|---|---|---|---|
key | true | string | null | 惟一标识符 |
title | true | string | null | 显示名称 |
primary | false | boolean | false | 主键 若是不指定主键, 不能update/delete, 但能够insert; 若是指定了主键, insert/update时不能填写主键的值; |
showType | false | string | input | 显示类型 input/textarea/inputNumber/datePicker/rangePicker/radio/select/checkbox/multiSelect/image/file/cascader/editor |
disabled | false | boolean | false | 表单中这一列是否禁止编辑 |
addonBefore | false | string/ReactNode | null | showType 为input能够设置前标签 |
addonAfter | false | string/ReactNode | null | showType 为input能够设置后标签 |
placeholder | false | string | null | 默认提示文字 |
format | false | string | null | 日期类型的格式 |
showInTable | false | boolean | true | 这一列是否要在table中展现 |
showInForm | false | boolean | true | 是否在新增或编辑的表单中显示 |
validator | false | boolean | null | 设置校验规则, 参考https://github.com/yiminghe/a... |
width | false | string/number | null | 列宽度 |
options | false | array | null | format:[{ key: '', value: '' }]或string。showType为cascader时,此字段暂不支持Array,数据只能经过异步获取。 |
min | false | number | null | 数字输入的最小值 |
max | false | number | null | 数字输入的最大值 |
accept | false | string | null | 上传文件格式限制 |
sizeLimit | false | number | 20480 | 上传文件格式限制 |
url | false | string | null | 上传图片url。图片的上传接口, 能够针对每一个上传组件单独配置, 若是不单独配置就使用config.js中的默认值;若是这个url是http开头的, 就直接使用这个接口; 不然会根据config.js中的配置判断是否加上host |
sorter | false | boolean | false | 是否排序 |
actions | false | array | null | 操做 |
参数 | 必填 | 类型 | 默认值 | 说明 |
---|---|---|---|---|
keys | false | array | null | 容许更新哪些字段, 若是不设置keys, 就容许更全部字段 |
name | true | string | null | 展现标题 |
type | false | string | null | update/delete/newLine/component |
参数 | 必填 | 类型 | 默认值 | 说明 |
---|---|---|---|---|
key | true | string | null | 惟一标识符 |
title | true | string | null | 显示名称 |
placeholder | false | string | null | 提示语 |
showType | false | string | input | 显示类型, 一些可枚举的字段, 好比type, 能够被显示为单选框或下拉框 input, 就是一个普通的输入框, 这时能够省略showType字段 目前可用的showType: input/inputNumber/datePicker/rangePicker/select/radio/checkbox/multiSelect/cascader |
addonBefore | false | string/ReactNode | null | showType 为input能够设置前标签 |
addonAfter | false | string/ReactNode | null | showType 为input能够设置后标签 |
defaultValue | false | string/array/number | null | 多选的defaultValue是个数组 |
min | false | number | null | showType为 inputNumber 时可设置最小值 |
max | false | number | null | showType为 inputNumber 时可设置最大值 |
options | false | array | null | options的key要求必须是string, 不然会有warning normal-format: [{"key": "", "value": ""}] cascader-format: [{"value": "", "label": "", children: ["value": "", "label": "", children: []]}] 若是值为string,表明异步获取的数据,则获取当前命名空间下该key对应的值 |
defaultValueBegin | false | string | null | showType为 rangePicker 时可设置默认开始值 |
defaultValueEnd | false | string | null | showType为 rangePicker 时可设置默认结束值 |
placeholderBegin | false | string | 开始日期 | showType为 rangePicker 时可设置默认开始提示语 |
placeholderEnd | false | string | 结束日期 | showType为 rangePicker 时可设置默认结束提示语 |
format | false | string | null | 日期筛选格式 |
showInSimpleMode | false | boolean | false | 在简单查询方式下展现,若数据中有一项包含此字段且为true的值,则开启简单/复杂筛选切换 |