前言
本小碼蟻呢,趴在低代碼這個深坑中也有一段時間了,很久沒更文其實也不是沒有值得的寫,可能是因為最近心氣浮躁,感覺寫啥都覺得不夠格,也可能是因為呢,每每動起筆來就覺得目的性太強,不是寫文的感念和思想,所以總是興致而來,草草而收,不甚寥奈。
今天要講的這個東西呢,它其實是低代碼系統(tǒng)中新興的一個玩具,它的主要功能呢,就是將表單的校驗邏輯可視化。這個玩具在平時的碼碼中其實是沒有太大用處的,之所以出現(xiàn)在低代碼中呢,主要是因為理念契合,它是邏輯可視化的一小步。
什么是公式編輯器?
講了這么多,那么公式編輯器 究竟是個啥子呢 ?請看下圖:
我們先解釋一下圖中的三個部分:
- 區(qū)域 1 是公式編輯器的輸入部分,這個部分是一個代碼編輯器,這個代碼編輯最核心的特性是,它可以對 “表單字段” 和 “函數(shù)” 進行聯(lián)想輸入。
- 區(qū)域 2 是公式編輯器的可用參數(shù)列表,這些字段都是從表單設計器中動態(tài)獲取來的。
- 區(qū)域 3 是公式編輯器為提供的邏輯組合公式,你可以把它理解成一個 js 工具庫,只不過我們現(xiàn)在把它可視化了,它里面涵蓋了數(shù)字處理(例如:求平均數(shù)、求和、求絕對值等等)、文本處理和時間處理等等。
為什么說它是邏輯可視化的一小步呢 ?我們可以來模擬一種場景,我們現(xiàn)在使用低代碼設計器設計了一張表單,這個表單里在提交之前需要對所有的字段都做一層驗證,但是我們事先都不知道驗證的規(guī)則,只有在設計這張表單時才知道,所以,我們現(xiàn)在有兩條路:第一,手寫邏輯代碼注入;第二,利用 公式編輯器 編寫規(guī)則注入。
其實對于編碼人員來說,手寫邏輯代碼更靈活,但是 公式編輯器 的這種形式讓更多人能參與這部分的編碼成為了可能,這是低代碼關于邏輯可視化的一個好的開始。
公式編輯器的執(zhí)行原理:
這部分業(yè)務的核心在于 編輯 與 翻譯。
formula-editor-react
作為公式的輸入端,公式編輯器其實是比較重要的一環(huán),完善的編輯器甚至能檢驗出輸入規(guī)則的符合法性。這部分呢,我并沒有花太多時間去造輪子,而是直接在 npm 庫里面物色了一個:formula-editor-react。
作者大哥貼的實際效果圖(實際跑起來也是保真的):
這個插件實現(xiàn)了關鍵字喚起,字段聯(lián)想等,對于使用來說已經(jīng)足夠了,但是這個插件本身還是有一些坑,我們來細說一下。
formula-editor-react 的問題與對策
使用
插件的使用比較簡單,像普通組件一樣引入使用就行:
// 模擬的表單字段const fieldList = [ { name: "銷量", value: "xl"}, { name: "單價", value: "dj"}, { name: "批發(fā)價", 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} // 需要調(diào)用組件中插入value的方法時使用,提供insertValue()方法 theme="day" // 主題 height={200} // 高度 defaultValue={defaultCode} // 初始化值 fieldList={fieldList} // @喚起 methodList={methodList} // #喚起 normalList={normalList} // 自定義無需校驗關鍵詞 readOnly={false} // 是否只讀 lineNumber={true} // 是否顯示列數(shù) onChange={getCode} // 回調(diào)></FormulaEdit>
問題1:代碼編號被覆蓋
插件在常規(guī)啟用之后,樣式會出現(xiàn)一點問題:
編輯器的行號會被內(nèi)容遮蓋,需要稍微調(diào)一下:
.CodeMirror-line { padding-left: 30px !important; box-sizing: border-box;}
問題2:API 不夠用
下面是作者貼出來的 API,這個插件的底層還是依賴的大名鼎鼎的 codeMirror,還不知道 codeMirror 的同學可以去百度了解一下,可能以后就會派上用場。
為啥說它不夠用呢 ?我們有兩個很常規(guī)的場景它無法滿足:第一,我希望每次打開編輯器彈窗的時候?qū)⒕庉嬈髑蹇眨坏诙蚁M看未蜷_編輯器時編輯器能自動聚焦并開啟光標閃爍。
這兩個場景是不是再普通不過了?但是這位作者大哥的文檔不支持,于是,我就把插件的實例打印出來瞅了瞅:
然后發(fā)現(xiàn),這個 CodeMirrorEditor 那是異常眼熟,但是很遺憾,它里面也沒有什么可用的突破口。直到,我點開了它的原型對象:
大家請看,這不就是 codeMirror 的 API 嗎,也就是說,這個 CodeMirrorEditor 就是 codeMirror 實例。
但是這個地方,還有個問題大家要注意一下,我去翻了一下他的 package.json 文件,他這個地方用的是 5.x 版本。codeMirror 在 6.x 之后進行了大刀闊斧的更新,用法與 5.x 是完全不一樣的,所以,這個時候我們要去翻 5.x 的 API。
過程我就省略了,這里我直接告訴大家結果吧:
// 編輯器聚焦editRef.current.focus()// 編輯器清空上一次輸入editRef.setValue('')
問題3:動態(tài)更改字段后,語法校驗失效
什么意思呢 ? 這個插件本身做了一層比較簡單的輸入語法校驗,比如說,我們現(xiàn)在表單里有兩個字段:
const fieldList = [ { name: "銷量", value: "xl"}, { name: "單價", value: "dj"}]
現(xiàn)在,我們在編輯框里輸入 @銷量 ,編輯器的語法驗證是通過狀態(tài)。但是我如果胡亂輸入了一些其他字符 @銷量balabala ,這個時候編輯就會反饋說,你輸入的字符不符合規(guī)則,你拿到這個提示之后呢就可以反饋給使用者,或者在保存之前做一些攔截性的措施。
那么,這個警告信息在哪兒看呢 ?
const getCode = (code, e) { ...}<FormulaEdit ... onChange={getCode} // 回調(diào)></FormulaEdit>
onChange 鉤子函數(shù)會在用戶每次進行輸入時調(diào)用,第一個參數(shù) code 能即時獲取到當前編輯器的內(nèi)容,而參數(shù) e 中則會反饋出插件的詳細信息,包括輸入代碼是否有語法問題。
那么,我們要講的問題是什么呢 ?問題是,我們現(xiàn)在更新了表單字段,往里面加了一個字段:
const fieldList = [ { name: "銷量", value: "xl"}, { name: "單價", value: "dj"}, { name: "批發(fā)價", value: "pfj"}]
此時,插件的驗證語法驗證功能對 @批發(fā)價 是失效的,當你輸入 @批發(fā)價 時,onChange 中會報告代碼錯誤。
這個問題有點棘手,我需要去看一下他的更新邏輯去讓插件重載一下參數(shù)才行,但是大哥并沒有提供源碼,只有一個打包后的文件。
github 上我也去翻找了一遍,大哥并沒有把項目傳上去 …
咋個辦 ?我把上面插件實例里的可用方法翻了兩三遍,把所以帶 up、update 等等跟更新沾邊的方法都試了一遍,還是沒個結果,于是乎,我動起了打包后的代碼的主意,好在確實讓我發(fā)現(xiàn)了端倪:
好家伙,他居然把這些數(shù)據(jù)存進了 localStorage 里,于是我趕忙翻了一下 localStorage
好了,故事講到這里,往往是我要說結論了。沒錯,我們只要在每次更新完表單字段之后,順便把它往 localStorage 存一下,它這個語法驗證就會生效了。
其實它這個驗證,并不是真正的語法驗證,只是對既有的表單字段和公式庫做了一個字符對比,沒有深奧的語法分析等等。
語法翻譯的新思路
這一節(jié)其實是這篇文章真正要講的內(nèi)容,當我們走完規(guī)則的編輯和保存流程,來到對規(guī)則數(shù)據(jù)的使用這一步的時候,我們要怎么去做?
我們還是來舉個例子,比如,我們在表單設計之初,編寫了下面這樣一條規(guī)則:
#AVERAGE(@參數(shù)1,@參數(shù)2,@參數(shù)3)
這條規(guī)則在我們從數(shù)據(jù)庫拿出來時,它只是一個字符串,而我們要做的,就是把它翻譯成 js 代碼去執(zhí)行。
面對這個問題,我的第一個念頭是,第一步,我需要從公式庫中識別出 AVERAGE 這個函數(shù),然后通過正則拿到 (…) 里的所有參數(shù),然后把參數(shù)傳入,并執(zhí)行函數(shù) AVERAGE 就可以了。直白來講,就是我得先拆分字符,并將公式函數(shù)與表單字段一一翻譯解析,最后求出結果。
但是,公式編輯器其實是支持 與 或 與 四則 的,也就是,極端狀況下,我們需要解析的公式有可能會變成這樣:
(#AVERAGE(@參數(shù)1,@參數(shù)2,@參數(shù)3) > @參數(shù)4) 或 (@參數(shù)5 === @參數(shù)6)
這個可就麻煩了,麻煩的不在于參數(shù),而在于運算符號,我們?nèi)绻シg這些運算符號,那可就不止一點兩點的坑了。
咋個辦 ?
好,講到這里,又到了我揭曉結論的時候了。大家想一想,這個公式字符串,和 js 代碼是不是很像 ?
get 到?jīng)]有 ?
是的,我們完全可以不用去翻譯它,我們就去執(zhí)行它!
執(zhí)行字符串式的 js 代碼,我們有兩種方案:第一,eval 函數(shù);第二,Function 構造函數(shù)。在這個,我選擇了第二種,原因有兩點:第一,eval 無法傳承,而 Function 可以;第二:eval 無法 return 執(zhí)行結果,而 Function 可以。
Function() 構造函數(shù)創(chuàng)建了一個新的 Function 對象。直接調(diào)用構造函數(shù)可以動態(tài)創(chuàng)建函數(shù),但可能會經(jīng)受一些安全和類似于 eval()(但遠不重要)的性能問題。然而,不像 eval(可能訪問到本地作用域),F(xiàn)unction 構造函數(shù)只創(chuàng)建全局執(zhí)行的函數(shù)。
———— MDN
Function 使用示例:
const sum = new Function('a', 'b', 'return a b')console.log(sum(2, 6))// Expected output: 8
具體怎么做呢?
- 第一步,我們掃描一遍規(guī)則,拿到我們需要準備的 公式集 和表單 參數(shù)集
- 第二步,我們替換掉公式中的中文參數(shù),使用我們實際拿到的參數(shù)名,替換掉 或 與 等中文邏輯字符,使用 js 邏輯字符 || && 等
- 第三步,我們傳入準備好的 公式集 和表單 參數(shù)集 以及調(diào)整過的公式字符串,并執(zhí)行
code = 'retrun ' codeconst res = (new Function(code)).call({ ...params, ...funcs})
這里要注意一點的是,我們這里使用了 call 方法來注入?yún)?shù),我們需要把 #AVERAGE 和 @參數(shù) 都替換成 this.AVERAGE 和 this.參數(shù)
結語
本文呢,實際上更像是一篇踩坑日記,沒有非常硬核的技術突破,但是可能會為正在低代碼路上匍匐前進的同學們提供一點點幫助。
作者:smile_逸仙
鏈接:https://juejin.cn/post/7267927742796742691
版權聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權/違法違規(guī)的內(nèi)容, 請發(fā)送郵件至 舉報,一經(jīng)查實,本站將立刻刪除。