前言
本小码蚁呢,趴在低代码这个深坑中也有一段时间了,很久没更文其实也不是没有值得的写,可能是因为最近心气浮躁,感觉写啥都觉得不够格,也可能是因为呢,每每动起笔来就觉得目的性太强,不是写文的感念和思想,所以总是兴致而来,草草而收,不甚寥奈。
今天要讲的这个东西呢,它其实是低代码系统中新兴的一个玩具,它的主要功能呢,就是将表单的校验逻辑可视化。这个玩具在平时的码码中其实是没有太大用处的,之所以出现在低代码中呢,主要是因为理念契合,它是逻辑可视化的一小步。
什么是公式编辑器?
讲了这么多,那么公式编辑器 究竟是个啥子呢 ?请看下图:
我们先解释一下图中的三个部分:
- 区域 1 是公式编辑器的输入部分,这个部分是一个代码编辑器,这个代码编辑最核心的特性是,它可以对 “表单字段” 和 “函数” 进行联想输入。
- 区域 2 是公式编辑器的可用参数列表,这些字段都是从表单设计器中动态获取来的。
- 区域 3 是公式编辑器为提供的逻辑组合公式,你可以把它理解成一个 js 工具库,只不过我们现在把它可视化了,它里面涵盖了数字处理(例如:求平均数、求和、求绝对值等等)、文本处理和时间处理等等。
为什么说它是逻辑可视化的一小步呢 ?我们可以来模拟一种场景,我们现在使用低代码设计器设计了一张表单,这个表单里在提交之前需要对所有的字段都做一层验证,但是我们事先都不知道验证的规则,只有在设计这张表单时才知道,所以,我们现在有两条路:第一,手写逻辑代码注入;第二,利用 公式编辑器 编写规则注入。
其实对于编码人员来说,手写逻辑代码更灵活,但是 公式编辑器 的这种形式让更多人能参与这部分的编码成为了可能,这是低代码关于逻辑可视化的一个好的开始。
公式编辑器的执行原理:
这部分业务的核心在于 编辑 与 翻译。
formula-editor-react
作为公式的输入端,公式编辑器其实是比较重要的一环,完善的编辑器甚至能检验出输入规则的符合法性。这部分呢,我并没有花太多时间去造轮子,而是直接在 npm 库里面物色了一个:formula-editor-react。
作者大哥贴的实际效果图(实际跑起来也是保真的):
这个插件实现了关键字唤起,字段联想等,对于使用来说已经足够了,但是这个插件本身还是有一些坑,我们来细说一下。
formula-editor-react 的问题与对策
使用
插件的使用比较简单,像普通组件一样引入使用就行:
// 模拟的表单字段const fieldList = [ { name: "销量", value: "xl"}, { name: "单价", value: "dj"}, { name: "批发价", value: "pfj"}, { name: "采购价", value: "cgj"}, { name: "零售毛利", value: "lsml"},]// 模拟的公式库const methodList = [ { name: "平均值", value: "平均值(,)", realValue: "avg" }, { name: "最大值", value: "最大值(,)", realValue: "max" }, { name: "最小值", value: "最小值(,)", realValue: "min" }, { name: "求和", value: "求和(,)", realValue: "sum" }]<FormulaEdit className="code-editor" ref={editRef} // 需要调用组件中插入value的方法时使用,提供insertValue()方法 theme="day" // 主题 height={200} // 高度 defaultValue={defaultCode} // 初始化值 fieldList={fieldList} // @唤起 methodList={methodList} // #唤起 normalList={normalList} // 自定义无需校验关键词 readOnly={false} // 是否只读 lineNumber={true} // 是否显示列数 onChange={getCode} // 回调></FormulaEdit>
问题1:代码编号被覆盖
插件在常规启用之后,样式会出现一点问题:
编辑器的行号会被内容遮盖,需要稍微调一下:
.CodeMirror-line { padding-left: 30px !important; box-sizing: border-box;}
问题2:API 不够用
下面是作者贴出来的 API,这个插件的底层还是依赖的大名鼎鼎的 codeMirror,还不知道 codeMirror 的同学可以去百度了解一下,可能以后就会派上用场。
为啥说它不够用呢 ?我们有两个很常规的场景它无法满足:第一,我希望每次打开编辑器弹窗的时候将编辑器清空;第二,我希望每次打开编辑器时编辑器能自动聚焦并开启光标闪烁。
这两个场景是不是再普通不过了?但是这位作者大哥的文档不支持,于是,我就把插件的实例打印出来瞅了瞅:
然后发现,这个 CodeMirrorEditor 那是异常眼熟,但是很遗憾,它里面也没有什么可用的突破口。直到,我点开了它的原型对象:
大家请看,这不就是 codeMirror 的 API 吗,也就是说,这个 CodeMirrorEditor 就是 codeMirror 实例。
但是这个地方,还有个问题大家要注意一下,我去翻了一下他的 package.json 文件,他这个地方用的是 5.x 版本。codeMirror 在 6.x 之后进行了大刀阔斧的更新,用法与 5.x 是完全不一样的,所以,这个时候我们要去翻 5.x 的 API。
过程我就省略了,这里我直接告诉大家结果吧:
// 编辑器聚焦editRef.current.focus()// 编辑器清空上一次输入editRef.setValue('')
问题3:动态更改字段后,语法校验失效
什么意思呢 ? 这个插件本身做了一层比较简单的输入语法校验,比如说,我们现在表单里有两个字段:
const fieldList = [ { name: "销量", value: "xl"}, { name: "单价", value: "dj"}]
现在,我们在编辑框里输入 @销量 ,编辑器的语法验证是通过状态。但是我如果胡乱输入了一些其他字符 @销量balabala ,这个时候编辑就会反馈说,你输入的字符不符合规则,你拿到这个提示之后呢就可以反馈给使用者,或者在保存之前做一些拦截性的措施。
那么,这个警告信息在哪儿看呢 ?
const getCode = (code, e) { ...}<FormulaEdit ... onChange={getCode} // 回调></FormulaEdit>
onChange 钩子函数会在用户每次进行输入时调用,第一个参数 code 能即时获取到当前编辑器的内容,而参数 e 中则会反馈出插件的详细信息,包括输入代码是否有语法问题。
那么,我们要讲的问题是什么呢 ?问题是,我们现在更新了表单字段,往里面加了一个字段:
const fieldList = [ { name: "销量", value: "xl"}, { name: "单价", value: "dj"}, { name: "批发价", value: "pfj"}]
此时,插件的验证语法验证功能对 @批发价 是失效的,当你输入 @批发价 时,onChange 中会报告代码错误。
这个问题有点棘手,我需要去看一下他的更新逻辑去让插件重载一下参数才行,但是大哥并没有提供源码,只有一个打包后的文件。
github 上我也去翻找了一遍,大哥并没有把项目传上去 …
咋个办 ?我把上面插件实例里的可用方法翻了两三遍,把所以带 up、update 等等跟更新沾边的方法都试了一遍,还是没个结果,于是乎,我动起了打包后的代码的主意,好在确实让我发现了端倪:
好家伙,他居然把这些数据存进了 localStorage 里,于是我赶忙翻了一下 localStorage
好了,故事讲到这里,往往是我要说结论了。没错,我们只要在每次更新完表单字段之后,顺便把它往 localStorage 存一下,它这个语法验证就会生效了。
其实它这个验证,并不是真正的语法验证,只是对既有的表单字段和公式库做了一个字符对比,没有深奥的语法分析等等。
语法翻译的新思路
这一节其实是这篇文章真正要讲的内容,当我们走完规则的编辑和保存流程,来到对规则数据的使用这一步的时候,我们要怎么去做?
我们还是来举个例子,比如,我们在表单设计之初,编写了下面这样一条规则:
#AVERAGE(@参数1,@参数2,@参数3)
这条规则在我们从数据库拿出来时,它只是一个字符串,而我们要做的,就是把它翻译成 js 代码去执行。
面对这个问题,我的第一个念头是,第一步,我需要从公式库中识别出 AVERAGE 这个函数,然后通过正则拿到 (…) 里的所有参数,然后把参数传入,并执行函数 AVERAGE 就可以了。直白来讲,就是我得先拆分字符,并将公式函数与表单字段一一翻译解析,最后求出结果。
但是,公式编辑器其实是支持 与 或 与 四则 的,也就是,极端状况下,我们需要解析的公式有可能会变成这样:
(#AVERAGE(@参数1,@参数2,@参数3) > @参数4) 或 (@参数5 === @参数6)
这个可就麻烦了,麻烦的不在于参数,而在于运算符号,我们如果要去翻译这些运算符号,那可就不止一点两点的坑了。
咋个办 ?
好,讲到这里,又到了我揭晓结论的时候了。大家想一想,这个公式字符串,和 js 代码是不是很像 ?
get 到没有 ?
是的,我们完全可以不用去翻译它,我们就去执行它!
执行字符串式的 js 代码,我们有两种方案:第一,eval 函数;第二,Function 构造函数。在这个,我选择了第二种,原因有两点:第一,eval 无法传承,而 Function 可以;第二:eval 无法 return 执行结果,而 Function 可以。
Function() 构造函数创建了一个新的 Function 对象。直接调用构造函数可以动态创建函数,但可能会经受一些安全和类似于 eval()(但远不重要)的性能问题。然而,不像 eval(可能访问到本地作用域),Function 构造函数只创建全局执行的函数。
———— MDN
Function 使用示例:
const sum = new Function('a', 'b', 'return a b')console.log(sum(2, 6))// Expected output: 8
具体怎么做呢?
- 第一步,我们扫描一遍规则,拿到我们需要准备的 公式集 和表单 参数集
- 第二步,我们替换掉公式中的中文参数,使用我们实际拿到的参数名,替换掉 或 与 等中文逻辑字符,使用 js 逻辑字符 || && 等
- 第三步,我们传入准备好的 公式集 和表单 参数集 以及调整过的公式字符串,并执行
code = 'retrun ' codeconst res = (new Function(code)).call({ ...params, ...funcs})
这里要注意一点的是,我们这里使用了 call 方法来注入参数,我们需要把 #AVERAGE 和 @参数 都替换成 this.AVERAGE 和 this.参数
结语
本文呢,实际上更像是一篇踩坑日记,没有非常硬核的技术突破,但是可能会为正在低代码路上匍匐前进的同学们提供一点点帮助。
作者:smile_逸仙
链接:https://juejin.cn/post/7267927742796742691
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。