做者:橙子前端
說到頁面可視化搭建,想必不少同窗都有所瞭解,業內已有很是多文章介紹,具體能夠查看底部傳送門,本文僅從 如何搭建一個易用、可擴展的通用可視化搭建工具 出發,探索技術思路,以及在實際實踐中思考,歡迎互相探討。vue
關於相關工具,業內開源及商業化產品很是多,可是通用的、知足定製化業務的卻很難找到,緣由有不少:react
因爲以上某些緣由,致使一些開源工具不能很好的在實際業務落地,不少時候就只能本身開發,或基於開源二次改造。git
爲何要嘗試作一個通用的可視化搭建工具呢?github
可視化配置工具做爲一種提效工具,若是隻是爲了知足自身業務就搞一套,從更大範圍看,是提效了仍是減效了?即便一個團隊、一個部門能夠作到通用,整個公司卻不必定,就會遇到常被DISS的「重複造輪子」,解釋起來基本就是有自身定製的需求,別的工具不能知足。所以也很難造成統一的可視化配置組件及規範。web
雖然有不少定製化場景及偏好問題,但從技術層面來看,有不少類似的地方:json
一、須要設計器,可添加、拖拽、配置組件
二、提供渲染能力
三、組件間可通訊
四、表單場景可聯動等markdown
假設設計器能定製,不一樣組件庫實現的可視化組件可在不一樣設計器中運行,經過底層一套schema或DSL規範約束。這樣就能很大程度解決組件共享問題,從而大幅減小重複開發成本。實現一個設計器及定製組件並不難,難的是如何達成這樣的規範,同時支持擴展。組件和設計器只是上層實現而已。app
借用一句毛爺爺的話:異步
道路是曲折的,前途是光明的
下面具體來看本身在嘗試實現過程的一些思考
如下爲比較典型的業務可視化配置場景:
場景 | 用戶 | 特色 | 用途 |
---|---|---|---|
運營活動 | 運營同窗 | 數量多,定製化強、需快速上線 | 通常配置運營活動、落地頁、抽獎等 |
流程表單 | 流程實施 | 對錶單能力要求高,表單內外聯動、公式計算等 | 配合流程設計,實現業務流轉 |
業務報表 | 產品、運營等 | 以查詢表單+可定製列的表格以及圖表配置爲表明 | 對流程及業務結果展現 |
個性化頁面 | 普通用戶 | 配置應更簡單,交互要求高 | 個性化訴求。如用戶主頁、定製工做臺等 |
中後臺頁面 | 前端研發 | 須要有代碼擴展能力、專業性強 | 前端提效。解決繁瑣、重複開發 |
固然以上也只是可視化配置的幾種典型場景,若是配置化能力足夠強,或許基於此,解決前端大部分開發工做也不是不可能。
從設計器開發到最終使用,涉及不一樣角色的用戶:
從配置難度來看,可視化工具一般有如下幾種:
能夠看下【可視化搭建工具與頁面】
能夠發現,LowCode
、NoCode
、ProCode
都能實現最終頁面(藍色)。ProCode
能力是最強的,能夠實現所有場景功能,同時還能實現LowCode
、NoCode
平臺或工具自己
以上按不一樣方式對可視化配置工具進行了分類,不必定很是準確,但基本都有所覆蓋。從技術出發,結合特色和訴求,如何實現這樣一個通用工具是一個值得探索的問題。
如下暫且列了部分表單問題、自定義訴求、通用能力三個方面典型問題
以上只是工具形式提供能力時可能碰見的幾個典型問題,固然問題遠遠不止這些,升級到平臺會涉及更多的問題。篇幅有限如下主要探索 表單場景 的典型問題,其餘問題留給後續探索。
以 輪播圖 組件爲例,不一樣專業程度用戶但願配置的屬性不一樣。
雖然最終展現結果相同,可是配置卻不一樣,這種狀況下是否能夠作成一個組件呢?我的以爲是能夠的,好比把更多屬性配置放在高級裏,或者讓組件之間能夠繼承等。
那麼一個組件應該具有哪些部分呢?
從示例能夠發現,首先須要有最終展現部分,其次須要有配置部分,還須要定義配置項。這裏將最終展現的部分稱View
,配置部分稱Setting
,定義配置稱爲Schema
。其關係大體以下:
Setting
與View
經過Schema
關聯起來,Schema
實例化後爲json
數據可保存到服務端。Setting
表單修改Schema
,Schema
變化影響View
變化。
表單規則定義探索及如何自定義規則?
從主流組件庫來看,不考慮聯動校驗規則狀況下,輸入框比下拉框、單選、時間等組件的規則要複雜些。後者只須要作選擇,通常增長是否必填規則便可,而前者除了必填,還有對字符作特別校驗。一般 用戶可輸入的組件比提供選項選擇的組件在規則上要複雜些。
表單組件通常都至少有一條規則,如 必填,固然也有例外,好比 開關組件(
switch
),無論是true
仍是false
必填對其來講都沒有意義
所以可在組件schema
上能夠定義required
字段表示時候必填,如:
{
"name": "firstName",
"required": true,
"errorMessage": "這是必填項"
}
複製代碼
對於只須要必填規則的組件來講,這樣定義彷佛並無什麼問題。然而不少時候一個組件每每有多個規則同時生效,如:但願該字段必填,能配置對應錯誤信息,同時還要求字符串長度有限制,對應過長或太短都能給相應的錯誤提示。用以上定義就不太好知足了,因而能夠升級一下:
{
"name": "firstName",
"rules": [
{ "required": true, "message": "這是必填項" },
{ "min": 3, "message": "最小長度不能小於3" },
{ "max": 10, "message": "最大長度不能超過10" }
]
}
複製代碼
這樣看起來清晰了不少,同時支持多條規則組合。這也是主流UI組件庫都在用的表單校驗 async-validator。rules
字段應與 async-validator 在使用上保持一致,這樣就能夠利用第三方庫作規則校驗了,
由於表單基本都有一條必填規則,能夠約定rules
字段第一個規則爲必填,其他規則根據實際狀況由配置人員動態添加。
注意schema.rules
中的每條規則字段類型與async-validator並不是一一對應,緣由是咱們的schema
將以json
的形式保存到服務端或本地,因此一些特殊字段如自定義校驗函數或正則等,就必須轉成相應字符串了。
async-validator
字段規則描述:{
"type": "string",
"validator": (rule, value) => value === 'test',
"message": "請輸入 test"
}
複製代碼
schema.rules
中單條規則描述{
"type": "string",
"validator": "(rule, value) => value === 'test'",
"message": "請輸入 test"
}
複製代碼
所以,設計器底層須要對錶單規則提供解析模塊(Rule
)。這個只是實現規則層面,對配置層面的話,讓配置人員寫這些代碼實在有些勉強,而提供可視化的方式選擇或簡單填寫就頗有必要,以下圖:
經常使用規則能夠內置到設計器底層。實際業務中,每每會有自定義的複雜規則,或者異步校驗等,那麼:
如何能配置規則的同時,還能根據不一樣業務場景擴展規則呢?
這裏就要求設計器對錶單規則有擴展能力。一種多是在配置的時候,直接經過腳本實現規則,僅適用於前端開發。第二種是組件開發同窗,提早開發好規則,而後建立設計器時擴展規則,最後在配置規則時選擇便可。這裏討論第二種實現。
// ./PhoneRule.js
export default class PhoneRule {
static get type () {
return 'phone'
}
static get name () {
return '手機號' // 用於可視化顯示
}
constructor (rule = {}) {
const defaultRule = {
type: 'pattern',
pattern: '',
message: '手機號不正確'
}
this.origin = Object.assign({}, defaultRule, rule)
this.rule = {
type: 'pattern',
trigger: 'blur',
pattern: /^1[3-9]\d{9}$/g,
message: ''
}
this.update(this.origin)
}
update (rule) {
if (rule) {
this.rule.message = rule.message
Object.assign(this.origin, rule)
}
}
}
複製代碼
import { Rule } from 'epage-core'
import PhoneRule from './PhoneRule.js'
Rule.set({ PhoneRule })
// 應用規則:PhoneRule的type靜態屬性對應phone
helper.setValidators(widgets, { input: ['phone'] })
// 傳入規則
new Epage({
Rule,
// ...
})
複製代碼
input
組件時,能夠看見增長了 手機號 規則這裏先給一個我的理解的聯動定義
表單聯動通常是指 一個或多個表單字段 的 值或屬性 發生 變化,使其餘 一個或多個表單字段 的 值或屬性 變化的交互。
這裏有幾個關鍵點:一個或多個表單字段、值或屬性、變化。
好比能夠爲如下任意聯動關係:
No. | 影響字段 | 關係 | 值 | 被影響字段 | 屬性 | |
---|---|---|---|---|---|---|
1 | 城市 | 屬於 | 中國 | --> | 學校 | 可選學校 |
No. | 影響字段 | 關係 | 值 | 被影響字段 | 屬性 | |
---|---|---|---|---|---|---|
1 | 城市 | 屬於 | 中國 | --> | 學校 | 可選學校 |
專業 | 可選專業 |
No. | 影響字段 | 關係 | 值 | 被影響字段 | 屬性 | |
---|---|---|---|---|---|---|
1 | 城市 | 屬於 | 中國 | --> | 學校 | 可選學校 |
2 | 在校人數 | 大於 | 1萬 |
1
與2
之間多是 且 也多是 或 的關係
No. | 影響字段 | 關係 | 值 | 被影響字段 | 屬性 | |
---|---|---|---|---|---|---|
1 | 城市 | 屬於 | 中國 | --> | 學校 | 可選學校 |
2 | 在校人數 | 大於 | 1萬 | 專業 | 可選專業 |
1
與2
之間多是 且 也多是 或 的關係
注意:
且
也多是或
關係等於
、屬於
等多關係與值創建條件且
與或
的關係a
字段影響b
字段,b
字段影響c
字段等,可經過多個兩級關聯配置以上是基於影響字段角度考慮關聯。固然也能夠從被影響字段的角度考慮關聯,在一些時候更直觀,如:
{
"widget": "input",
"name": "c",
"hidden": "$a.hidden === false && $b.hiden === true"
}
複製代碼
以上schema描述會有如下很差的地方:
一、會讓hidden
原本爲boolean
類型,卻變成了字符串表達式。
二、若是hidden
原本就是字符串類型的字段,又怎麼區分是具體值仍是表達式呢?固然也能夠在擴展字段
三、不一樣字段屬性邏輯比較分散,不方便統一管理
以上示例聯動中,影響字段
經過改變自身的表單值
來觸發聯動邏輯。這裏的值能夠是等於
關係,也能夠是包含
、小於
等關係,取決於值類型。如:
等於
、不等於
等於
、不等於
、包含
、不包含
等於
、不等於
、大於
、小於
、大於等於
、小於不等於
因爲不一樣表單組件值類型可能不一樣,因此能夠做爲靜態屬性定義到組件的Schema
上,如:
class InputSchema extends FormSchema {}
Object.assign(InputSchema, {
logic: {
value: ['=', '!=', '<>', '><'] // [等於, 不等於, 包含, 不包含]
}
})
複製代碼
若是把 表單字段 當作一個對象,表單值(value
)當作一個特殊屬性,還有一些普通屬性,如顯隱(hidden)、禁用(disabled)等,就會發現聯動就是屬性與屬性之間邏輯綁定。如何作到監聽value
變化以及普通屬性變化呢?
value
之因此認爲是特殊屬性主要緣由:該屬性的變化會觸發
onchange
事件。對應hidden
、disabled
等普通屬性卻沒有,理論上也應該有onhidden
、ondisabled
相應事件。
若是把 表單字段 全部屬性定義成響應式,任意屬性變化時就能很方便通知到。也能夠本身實現訂閱發佈方式,來修改表單屬性。
值聯動
onchange
),聯動其餘表單字段屬性變化,這裏稱事件聯動
從必定程度講兩者方式都能解決部分相同功能的聯動,如A組件value值發生變化,也能夠認爲是A組件發生onchange
事件
如下以開發 epage 部分實現爲例分析(暫未實現多對1、多對多關聯邏輯)
首先,邏輯定義
定義schema
上應該保存的邏輯結構。具體邏輯定在單個組件的Schema
上仍是最外層Schema
均可以,這裏定義到統一的地方,方便管理。
主要定義 影響組件 和 被影響組件:包括聯動類型、影響表單組件值符合某種條件、被影響表單組件哪些屬性聯動、影響表單觸發的什麼事件等
{
// schema 其餘字段
logics: [
{
"key": "kB1mKTnek", // 影響組件key
"type": "value", // 關聯類型,值聯動 或 事件聯動
"action": "=", // 值聯動是相等關係,這裏定義不一樣符號,應該提供符號解析能力
"value": "show", // 具體值
"effects": [ // 被影響組件列表
{
"key": "kASJAJwRB", // 被影響組件key
"properties": [
{ "key": "hidden", "value": true }, // 被影響組件隱藏
{ "key": "disabled", "value": true } // 被影響組件禁用,還應能夠爲其餘屬性
]
}
]
}
]
}
複製代碼
其次,邏輯解析
基於以上分析,應具有值邏輯
和事件邏輯
。在渲染或預覽時執行生效
import EventLogic from './EventLogic'
import ValueLogic from './ValueLogic'
class Logic {
// 檢查值邏輯配置是否合法,是否有重複邏輯等
// 返回 { patches, scripts },對應比較結果和可能的自定義腳本
diffValueLogics(){}
// 同上
diffEventLogics(){}
// 根據以上比較結果,最終修改組件Schema屬性
applyPatches(){}
// 檢查被影響組件是否有效等
checkEffect(){}
}
複製代碼
對以上生成的 邏輯關係 進行解析。如值聯動中 action
字段就有不少比較關係(=
(等於)、!=
(不等於)、>
(大於)、<
(小於)、<>
(包含)等),以=
爲例:
class ValueLogic{
constructor () {
this.map = {
'=': {
key: '=',
value: '等於',
// left、right爲用戶輸入值都爲字符串,valueType爲應該的數據類型
// 但左右值類型與valueType不一致時,根據狀況進行轉換後比較
validator: (left, right, { valueType }) => {
const booleanMap = { true: true, false: false }
let leftValue = left
let rightValue = right
if (valueType === 'number') {
leftValue = parseFloat(left)
rightValue = parseFloat(right)
return (isNaN(leftValue) || isNaN(leftValue)) ? false : leftValue === rightValue
} else if (valueType === 'boolean') {
if (right in booleanMap) {
rightValue = booleanMap[right]
}
}
return leftValue === rightValue
}
},
// ...
}
}
}
複製代碼
爲了讓設計器更具備通用性,邏輯關係定義及解析也應支持組件開發者擴展。
具體值邏輯或事件邏輯的一些實現能夠參考 epage#Logic
作一個可視化配置工具並不難,可是既要保證通用,又能保證擴展性,同時統一標準一塊兒共建卻不容易。須要創建一套統一Schema
或 DSL,不一樣開發者能認同,可根據須要擴展定製,進而達到快速實現業務交付目標。
傳送門:
滴滴效能平臺前端團隊EFE,感召於經過技術持續提高組織效能的組織使命,致力於打造技術領先的前端技術團隊,深耕於性能監控、質量監控、低代碼配置、文檔協做、微前端、webIDE等多個領域,技術方向廣闊,探索空間和成長空間極大。
咱們是一個充滿激情和有夢想的團隊,期待您的加入。感興趣的可聯繫 dumingtan@didiglobal.com