面試被問到低代碼細節(jié)?聽我這樣吹(含架構和原理)(低代碼開發(fā)面試)
寫在前面
一直以來我總是會刷到一些關于低代碼的文章,但都是零零散散的,根本記不住?,F(xiàn)在終于有時間在這里做個系統(tǒng)性的總結了
關于低代碼,想必大家都有所了解,就是用拖拉拽的方式來快速搭建某個頁面 || 表單 || 海報 || 游戲等,但其實除了常見拖拉拽的方式以外,還可以用形如在線文檔的方式來生成頁面,這里分別貼個鏈接給大家體驗一下:
- 拖拽型的低代碼
- 類文檔型的低代碼
當然了,本文主要講解的是拖拽型的低代碼,這里先簡單截個圖瞅瞅:
順帶羅列下低碼平臺的一些優(yōu)缺點:
優(yōu)點:?
- 它的本質是提效,提效的同時給了自由度(以較少的成本達到想要的結果)
- 它的優(yōu)勢是可視和快速
- 它的能力源自于物料(組件)的能力
缺點:?
- 上手成本(我自己覺得門檻還是高了),除了海報大部分情況下還是要摸索很久的
- 前端框架日新月異,過一兩年來個改革,低碼平臺就得適配一遍,維護成本相當大;又或者平臺自身升級了,你也不知道會對自己的項目產(chǎn)生什么影響,但是不跟著升級后面就沒法迭代;并且基本上你用了一個平臺的低代碼,要想切換到其他平臺那就得從 0 到 1
- 不好維護和迭代,換個同類型組件都得考慮一下,如果換個人還得重新理解,還不好調試,不好定位問題,也不好確定本次改動對之前功能的影響
- 不好優(yōu)化(比如你現(xiàn)在想做性能優(yōu)化,完全沒有頭緒)
- 二次開發(fā)不可逆,很難做到持續(xù)可視化(確實,大部分情況是這樣?♀?)
- 隨便舉個反例,只要低代碼平臺沒有實現(xiàn)那就是缺點
- …
確實,低代碼自身限制還是很多的,隨便提個改動,都可能牽一發(fā)動全身。不過這里我們并不會去評判低碼平臺的好壞,而只是單純的分享一些低代碼中的核心思路和整個流程:包括但不限于模塊劃分、如何解耦和擴展、畫布的實現(xiàn)方式等等。
最重要的事
最重要
如果讓我說一個低代碼中最重要的事情,那就是方向。我們要知道低代碼是有它的適用場景的(交互簡單 && 輕邏輯),比如:
- 海報(我覺得這個方向應用的相當好,0 邏輯 0 交互)
- H5 運營活動頁(一次性的頁面也很適用,沒有維護的煩惱)
- 表單收集頁(問卷調查也不錯,也是一次性)
- 中后臺頁面
- 2D 游戲(其實我覺得很多低代碼思想是從游戲引擎借鑒過來的,因為大家可能比較少觸碰,所以這里特地截了一個游戲開發(fā)時候的圖,和低代碼一毛一樣)
單單只實現(xiàn)上面的任意一種場景就已經(jīng)要兼容很多東西了,所以想覆蓋所有情況幾乎是不可能的,而且現(xiàn)在也沒有一個統(tǒng)一的規(guī)范,都是各做各的,百花齊放。因此不要想著啥都做,得挑一個垂直方向發(fā)力,方向越具體,自動化程度越高(比如組件拿來即用,無需修改),生產(chǎn)效率也更高,平臺也會更好用。
次重要
除了方向,次重要的事情就是簡單(包括交互和邏輯)。為什么要簡單呢?
- 因為一旦復雜就不可視了,本來拖拽的優(yōu)勢也沒有了
- 通常情況下,低碼平臺只實現(xiàn)了視圖可視化,并沒有實現(xiàn)邏輯可視化,邏輯一復雜還是得寫代碼。根本原因是我們很難將邏輯進行可視化,它不如寫代碼來的干脆明了。當然目前也有比較適合于邏輯可視化的場景(前提是邏輯比較固定),比如審批流和Scratch,也都特地截了個圖:
那如何才能做到簡單呢?還是得朝著垂直方向發(fā)力,也就是會有點定制,要固化一些操作、約束一些行為。目前我還是覺得低代碼主要還是給非研發(fā)同學用的,所以要足夠簡單。
基本實現(xiàn)
扯了這么多,現(xiàn)在讓我們趕緊步入正軌吧。縱觀大部分低碼平臺,主要都是由以下四部分組成(畫的有點簡陋):
接下來我們會對每個部分都挑兩三個重點來講解一下。
1、協(xié)議
在講解每個模塊之前,我們先來說一個東西,就是協(xié)議(聽起來很高大上,其實就是規(guī)范,更樸素點叫做格式),它主要包括物料協(xié)議、平臺搭建協(xié)議和其他協(xié)議等等。為什么要先約定協(xié)議呢,因為這個東西貫穿低代碼的整條鏈路:
- 當我們想擴展物料時,需要實現(xiàn)相應的協(xié)議
- 當我們把左側一個物料拖到中間畫布區(qū)時,需要通過協(xié)議來通信和解析
- 當我們選中畫布區(qū)域的組件時,要想通過右邊設置面板進行屬性設置時,也需要通過協(xié)議來通信和解析
- 當我們準備預覽和發(fā)布代碼時,也需要通過協(xié)議來生成代碼
協(xié)議是低碼平臺的基石,它的主要目的就是約束和擴展,約定的好,事半功倍;約定不好,版版重構。約定優(yōu)于配置說的就是這個道理。如果維護到后期發(fā)現(xiàn)協(xié)議很難拓展了,那基本只能重來了,只不過你多了些經(jīng)驗。協(xié)議本質上就是一堆 interface(就是固定格式啦)。
2、物料區(qū)
先來看看最左側的物料區(qū)吧,這是低代碼的起點,協(xié)議也是從這邊開始的,先約定個最簡單的物料協(xié)議吧:
/** 單個物料約定 */interface IComponent { /** 組件名 */ componentName: string; /** 組件中文名稱 */ title: string; /** 縮略圖 */ icon?: string; /** 包地址 */ npm: { /** 源碼組件名稱 */ componentName?: string; /** 源碼組件庫名 */ package: string; /** 源碼組件版本號 */ version?: string; }; /** 分類:比如基礎組件、容器組件、自定義組件 */ group?: string; /** 組件入?yún)⒒蛘哒f是可配置參數(shù) */ props?: { name: string, propType: string, description: string, defaultValue: any, }[]; /** 其他擴展協(xié)議 */ [key: string]: any;}// 舉個例子const componentList = [ { componentName: "Message", title: "Message", icon: "", group: "基礎組件", npm: { // import { Message } from @alifd/next 的意思 exportName: "Message", package: "@alifd/next", version: "1.19.18", main: "src/index.js", destructuring: true, }, props: [{ name: "title", propType: "string", description: "標題", defaultValue: "標題" }] }];
物料區(qū)其實沒啥功能,我們會有一個 componentList,里面是各種組件的基本信息,直接循環(huán)渲染即可。同時還會順便生成一個 componentMap,主要是方便后續(xù)我們通過組件名來快速獲取是組件的元信息,比如下面這樣:
const componentMap = { Message: { componentName: "Message", title: "Message", icon: "", group: "基礎組件", // ... },};// 通常情況,低碼平臺平臺還需要對外暴露出加載組件和注冊組件的方法,比如這樣:function createRegisterConfig() { const componentList = []; const componentMap = {}; return { componentList, componentMap, loadComponent: () => {}, register: (comp) => { componentList.push(comp); componentMap[comp.componentName] = comp; } }}
物料區(qū)本身并不復雜,這里就說三個注意點:
- 組件的分類:
- 容器組件(這類組件主要用來協(xié)助布局和嵌套)
- 基本組件(常見的有圖片、文本、輸入框、視頻等)
- 集成度稍高一點的組件(表格、表單、圖表、自定義組件、業(yè)務組件)
- 如何加載組件?不管什么情況下,在前端加載文件只有兩種方式:
- 一個是 import()
- 一個是 <script>
- 這里也簡單貼個示例代碼:
// 方法一:importconst name = 'Button' // 組件名稱const component = await import('https://xxx.xxx/bundle.js')Vue.component(name, component)// 方法二:scriptfunction loadjs(url) { return new Promise((resolve, reject) => { const script = document.createElement('script') script.src = url script.onload = resolve script.onerror = reject })}const name = 'Button' // 組件名稱await loadjs('https://xxx.xxx/bundle.js')// 這種方式加載組件,會直接將組件掛載在全局變量 window 下,所以 window[name] 取值后就是組件Vue.component(name, window[name])
- 如何擴展自定義組件?
- 這是最為重要的功能之一,我們要想讓一個第三方組件在當前平臺可用,直接拿來肯定是不行的,必須要進行適配:
- 如果這個第三方組件是已有的,我們需要簡單適配一下(適配器模式),也就是這個第三方組件需要實現(xiàn) IComponent 這個接口
- 如果這個第三方組件是待開發(fā)的,那低碼平臺一般會提供腳手架讓使用方去基于一個固定的模板去開發(fā),這樣協(xié)議自然也就對上了
- 通常情況下,低碼平臺自身會有個物料管理平臺對組件進行統(tǒng)一的管理和操作,簡單點做的話我們可以直接把開發(fā)好的組件發(fā)到 npm 上,把 npm 當做物料平臺用
- 但我們更期望的是物料統(tǒng)一,既然用了別人的平臺,那就去用別人的組件,盡量去復用平臺自身已有的組件庫。我們不生產(chǎn)組件(或者少生產(chǎn)),只是消費組件。
3、畫布區(qū)
這里我們先說說畫布區(qū)的幾種實現(xiàn)方式吧:
- 自由畫布:拖到哪里元素就放到哪里,配合容器組件可以協(xié)助布局以實現(xiàn)自適應
- 流式布局:組件從左到右從上往下自然排列,比如 H5 就是單純的從上往下平鋪開來
- 自動布局:也是拖哪放哪,但是原來的地方如果已經(jīng)有組件則會被擠開,被擠開的元素還會有個遞歸擠開的過程
- 柵格畫布:類似前端組件庫中的柵格布局,一行 24 列,一行 12 列這樣劃分填充,看起來比較規(guī)范也比較整齊
- 網(wǎng)格畫布:類似棋盤一樣的網(wǎng)格,每個網(wǎng)格都是 8px(看 UI 規(guī)范),拖拽組件的時候會自動吸附到這些網(wǎng)格線上,這樣可以讓整體看起來更加錯落有致(如果你有一些設計規(guī)范,可能會需要用到這種畫布)
- 混合布局:效率和通用性的權衡
那畫布區(qū)是怎么渲染出來的呢?其實我們會有一個 componentTree 來遞歸渲染拖拽出來的元素,然后各自按照組件類型來渲染,先簡單看下 componentTree 的大體結構吧:
import ReactRenderer from '@alilc/lowcode-react-renderer';import reactDOM from 'react-dom';import { Button } from "@alifd/next";const componentTree = { // 畫布區(qū)的所有元素都在這里維護 componentName: 'Page', // 因為我們是以頁面為單位,所以頂層一定是 Page props: {}, children: [{ componentName: 'Button', props: { type: 'primary', size: "large", style: { color: '#2077ff' }, className: 'custom-button', }, children: '確定', }]};const conponents = { Button,};ReactDOM.render(( // 里面會遞歸渲染組件 <ReactRenderer componentTree={componentTree} components={components} /> ), document.getElementById('root'));// ================================================// 如果樹形結構不好理解的話,我們可以把數(shù)據(jù)換成普通的數(shù)組來理解,然后渲染的過程就是循環(huán)遍歷數(shù)組即可,這樣就容易多了,比如 H5 頁面就很適合數(shù)組這種形式const componentTree = [{ "componentName": "ElButton", "height": 100, "props": {}, "style": {}}, { "componentName": "ElInput", "height": 300, "props": {}, "style": {}}];
事實上,低碼平臺都秉持著數(shù)據(jù)驅動視圖的思想(和我們現(xiàn)在用的 vue 和 react 框架如出一轍),也是通過遞歸解析 componentTree 這個全局組件樹來動態(tài)生成頁面,化簡來說就是:UI = Transformer(componentTree)。Transformer 這一步我們可以稱之為轉換器、渲染器或者 render 函數(shù),通常開發(fā)完成之后,這個渲染器是不用改的,我們只需要單純的修改數(shù)據(jù),渲染器自然會幫我們解析。
這個思想很重要,也是解耦的核心:就是我們所有的操作,不管拖拽也好,修改元素屬性也好,還是調整元素位置,都是對 componentTree 這個數(shù)據(jù)進行修改,單純的對數(shù)據(jù)進行操作,比如追加元素,就往 componentTree 的 children 里面 push 一個元素即可;如果要修改一個元素的屬性值,只需要找到對應元素的數(shù)據(jù)修改其 props 值即可。畫布編排的本質就是操作組件節(jié)點和屬性。
另外為了讓每個組件都能直接獲取到這個 componentTree,我們可以把這個 componentTree 弄成全局的(全局數(shù)據(jù)能夠讓整體流程更加清晰),比如放在 window、vuex、redux 上,這樣每個模塊就能共享同一份數(shù)據(jù),也能隨時隨地更改同一份數(shù)據(jù)(平臺會暴露公共的修改方法),而這個渲染器只是單純的根據(jù)這個數(shù)據(jù)來渲染,并不處理其他事情。
這里我們還要注意一個問題,就是畫布區(qū)本身也是個組件,是個組件那它就會受到父元素和全局的影響,最簡單的比如樣式,可能受到外部樣式作用,導致你這個畫布區(qū)和最終呈現(xiàn)的頁面可能有一丟丟的不同,所以要排除這些影響,那具體可以怎么做呢?就是把這個畫布區(qū)搞成一個獨立的 iframe,這樣環(huán)境就比較純了,完美隔離,只不過增加了通信的成本?,F(xiàn)在,物料區(qū)只負責渲染組件列表,以及觸發(fā)拖拽放下的事件,之后就是觸發(fā)修改全局的 componentTree,再之后就是觸發(fā)畫布區(qū)的重新渲染。這樣一來,你就會發(fā)現(xiàn)畫布區(qū)和物料區(qū)就很好的解耦了。到目前為止畫布區(qū)只負責單純的渲染。
如果你還是想知道這個渲染器到底是怎么遞歸怎么渲染的,我這里也提供了段簡單的代碼幫助你理解:
function renderNode(node) { if (!node) return null; const component = components[node.componentName]; const props = compute(node.props); const children = node.children.map(c => renderNode(c)); return React.render(component, props, children);}renderNode( componentTree);
4、屬性設置區(qū)
接下來我們簡單講下右側的屬性設置區(qū),這個區(qū)域通常會支持三種基本的設置:props && 樣式 && 事件。一般來說我們的操作是這樣的:
- 點選畫布區(qū)的某個組件
- 觸發(fā)設置某個全局變量為當前組件
- 右側屬性面板就會根據(jù)當前組件的 componentName 從 componentMap 中找到組件對應的 setters(可配置項),這個 setters 其實就是一開始在物料協(xié)議里面約定的 props,但是和 props 可能有點小區(qū)別,需要自己手動寫個函數(shù)轉一下;或者可以直接在物料協(xié)議里面多添加一個 setters 字段來專門描述有哪些屬性可以支持配置。兩種方式都是可以的
- 然后也是單純的循環(huán)渲染 setters
- 再把當前組件的 state(初始值)賦值給 setters
上面的過程和我們平時開發(fā)中后臺應用的表單項(FormRender)是一毛一樣的,網(wǎng)上也有很多教程,這里就不細說了。
我們主要來講一下屬性設置區(qū)的幾個注意點:
- 首先如果我們修改了屬性設置區(qū)的表單項,我們實際上是去修改全局的 componentTree,然后畫布區(qū)自然就會根據(jù)這個新的 componentTree 自動渲染,有點單向數(shù)據(jù)流的意思(就是修改數(shù)據(jù)的入口只有一個),也方便排查問題。把數(shù)據(jù)放到全局上,很多通信的過程就可以省掉了
- 一個常常提到的問題就是如何實現(xiàn)聯(lián)動,比如字段 2 的顯隱依賴于字段 1 的值,類似這種功能通常有兩種實現(xiàn)方式:
- 一種類似發(fā)布訂閱,我們可以在字段 2 中監(jiān)聽(on)來自字段 1 的 emit 事件,只是多了你就很難知道各自有哪些依賴關系了
- 另一種方式就是利用全局數(shù)據(jù)了,比如我們把字段 1 和字段 2 都放在全局數(shù)據(jù)中,然后在字段 2 中新增一個 visible 的屬性設置器,其值是一個模板表達式,形如:{{ globalData.field1 && … && globalData.fieldN }},因為數(shù)據(jù)是全局的所以很方便能夠直接獲取到,在實際渲染的過程中就會動態(tài)執(zhí)行上面那個表達式來確定組件渲不渲染。此外,因為數(shù)據(jù)是全局的,跨組件或者跨頁面共享數(shù)據(jù)也會變得輕而易舉
- 再一個常常提到的問題就是如何處理點擊事件?如果做的開放點、簡單點,我們可以直接讓用戶自己寫函數(shù),然后運行的時候用 eval 或者 new Function 執(zhí)行一下就行。但是這樣會有個問題,就是安全性、穩(wěn)定性和效率不夠,所以我們需要進行一些限制,這個通常有兩種方法:
- 一種是暴露固定方法,只接收參數(shù),比如我這個點擊的結果就是跳轉到某個頁面,也就是執(zhí)行 window.open 這個方法,那我們就不允許用戶直接書寫這個代碼,而是先內置一個全局封裝好的 jumpToPage(url) 方法,然后在屬性設置的時候只允許輸入 url 并進行簡單校驗
- 但是固定方法是很難滿足我們的一些需求的,最終還是得支持讓用戶可以自己寫腳本,于是乎我們就得讓這個腳本具有良好的隔離性,也就是沙箱或者對代碼進行校驗等,這里就簡單說一下沙箱的方式:
- with new Function(用 with 改變作用域實現(xiàn)隔離、用 try catch 捕獲錯誤保障穩(wěn)定性)
- iframe 沙箱:在一個空的 iframe 里面執(zhí)行這些未知的代碼可以最大程度的實現(xiàn)隔離,其余方式都可以通過原型鏈進行逃逸(其實 iframe 通過 parent 也能逃逸)
- 等一個新的 API:ShadowRealm
下面是簡單的代碼示例截圖,有個印象就行:
那沙箱里面能拿到什么數(shù)據(jù)呢?其實主要看我們想暴露什么參數(shù)給組件了,比如全局數(shù)據(jù)、全局方法、父元素、當前組件的 state 等等,那其實我們就可以直接傳個包含以上數(shù)據(jù)的大對象然后傳給 with 即可。
5、頂部操作區(qū)
上面那幾個組成部分其實已經(jīng)構成了低代碼中最核心的幾個部分,我們可以稱之為 Core(內核)。對于頂部操作區(qū),通??梢杂星斑M、后退、清空等操作,但是這些操作并不算是必須的,它們通常以插件的形式存在,也方便大家一起維護和擴展,這就是微內核架構:
1 * Core N * plugins
關于這個架構,有興趣的可以參考這篇文章 微內核架構在前端的實現(xiàn)及其應用,這篇文章講解的很清楚了。
然后我們這里就簡單講下清空和回退操作吧(當然其余其他操作也是一樣的道理):
- 先說下清空,這個其實就很簡單了,就是直接把全局的數(shù)據(jù)清了就行,畫布區(qū)會因為數(shù)據(jù)變了而自動重新渲染(注意:我們做的任何操作都是先去修改那個全局數(shù)據(jù))。
- 再說說回退吧,對于大部分編輯器來說,這是一個很常見的功能。
- 簡單做的話就是每次有操作時,直接把整個 componentTree 復制一份,撤銷和恢復都直接重新賦值整個 componentTree 即可
- 另外一種方法就是基于操作來回退,源數(shù)據(jù)只有一份,但是有多個 actions,比如我添加了一個元素,對應 add(comp) 方法,那我回退的時候就是執(zhí)行它的反向操作 remove(comp) 來修改,麻煩的地方就在于每個操作都需要寫一個對應的反向操作的方法
但其實插件不僅僅適用于頂部條:
- 物料區(qū)也可以有插件:主要表現(xiàn)就是用腳手架來擴展物料
- 畫布區(qū)也可以有插件:比如選中組件的時候可以擴展復制、刪除、輔助線、上移下移等功能
- 設置區(qū)也可以有插件:比如顏色選擇器,可以追加自定義表單項
6、代碼生成
到目前為止,我們就初步搭建好了頁面,它背后其實是一堆數(shù)據(jù),但是有可能你還是一頭霧水,沒關系,為了能讓大家有個更具象的認識,我把這份數(shù)據(jù)的最終形態(tài)簡單匯總了下(放心,就一丟丟丟丟數(shù)據(jù)很好看懂的):
const json = { version: "1.0.0", // 當前協(xié)議版本號 componentList: [ { // 組件描述 componentName: "Button", package: "@alifd/next", version: "1.0.0", destructuring: true, }, { package: "@alifd/next", version: "1.3.2", componentName: "Page", destructuring: true, } ], state: { // 全局狀態(tài) name: "尤水就下" }, componentsTree: { // 畫布內容 componentName: "Page", props: {}, children: [{ componentName: "Button", state: {}, props: { type: 'primary', size: "large", style: { color: 'red' }, className: 'custom-button', onClick: { // 事件綁定 type: "JSFunction", value: "function(e) { console.log(e.target.innerText) }" }, } }] }}
接下來就是要準備發(fā)布了,具體該怎么做呢?這里說下兩種主要的方式:
- 同技術棧:如果用的技術棧和低代碼平臺是一樣的話,我們可以使用運行時渲染,開發(fā)一個 preview 的頁面,頁面里面有固定的渲染器(和畫布區(qū)有點像),也會加載對應的組件庫,剩下的就是遠程獲取頁面的 json 數(shù)據(jù)(也就是上面那一坨代碼,當然也可以稱之為 schema),傳遞給渲染器,然后直接展示即可。不理解?那就再簡單貼下代碼:
import React, { memo } from 'react';import ReactRenderer from '@alilc/lowcode-react-renderer';const SamplePreview = memo(() => { return ( // 至于渲染器怎么實現(xiàn)的,網(wǎng)上有一堆文章,不理解的同樣可以把數(shù)據(jù)當成簡單的數(shù)組,for 循環(huán)渲染而已 <ReactRenderer className="lowcode-plugin-sample-preview-content" schema={schema} components={components} /> );});// ps: 這里簡單說下 json 和 schema 的區(qū)別,以前我也很困惑,不知道現(xiàn)在理解對沒有(但我感覺大部分情況下都是隨便叫的)// json 是一個普通對象// schema 是一個有固定格式的普通對象// json schema 是一個描述 json 格式的普通對象
- 另一種就是直接導出項目,在此基礎上可以二次開發(fā)或者嘗試 ssr 渲染(但是一旦二次開發(fā)基本就是不可逆羅),就像下面這樣:
那兩種方式有什么區(qū)別呢:
前者 | 后者 | |
1 | 運行時編譯(JIT) | 預編譯(AOT) |
2 | 最終輸出的是 json | 最終輸出的是項目 |
3 | 實時生效 | 得打包重新發(fā)版 |
4 | 一般這個夠用 | 有性能要求用這個 |
為什么前者會有性能問題呢?因為渲染頁面的時候,需要將 json 進行一層轉換,而后者是已經(jīng)打包之后的項目了。
其他問題
- 模塊之間如何進行解耦呢:
- 通過全局的發(fā)布訂閱模式來通信
- 通過全局數(shù)據(jù)來共享,而不是通過直接相互傳遞數(shù)據(jù)
- 其實你把每個模塊都寫成獨立的項目,開發(fā)起來自然就會強迫自己解耦了
- 解耦最大的好處就是當你要重構或者替換某一個模塊時,可以直接替換單個模塊而不影響其他地方的邏輯
- 除了全局變量,我能在組件里面維護自己的狀態(tài)嗎?當然是可以的,我們可以新增一個 state 屬性,來維護組件自身的一些狀態(tài),并且我們可以通過維護原型鏈的方式來層層向上獲取父元素數(shù)據(jù),比如這樣:state.__proto__ = parent.state
- 怎么支持跨平臺:低代碼平臺搭建好后最終得到的是一個 json,而這個 json 本身就是以一種通用的語言來描述頁面結構、表現(xiàn)和行為的,它和平臺無關。要想跨平臺、跨框架,只需要做一個適配層去解析這些 json,導出成各平臺、各框架需要的樣子即可。舉個例子來說,我們在解析的過程中肯定會需要創(chuàng)建元素,而 vue 和 react 都有各自的 createElement 方法,那到時候只需要把創(chuàng)建元素的方法換成各框架各自的方法就行了。思路聽起來很簡單,但是每一種適配起來都很繁瑣
- 在哪里管理依賴呢:低碼平臺除了上面提到的模塊,一般還會有個物料管理平臺,它不僅用來管理我們的組件,也會管理我們的項目依賴,最終表現(xiàn)為 json 中的 packages 字段,就像下面這樣:
{"version": "1.0.0","packages": [{ // 依賴包 "title": "fusion 組件庫", "package": "@alifd/next", "version": "1.20.0", "urls": [ "https://alifd.alicdn.com/npm/@alifd/next/1.20.0/next.min.js", "https://alifd.alicdn.com/npm/@alifd/next/1.20.0/next.min.css" ], "library": "Next" // 最終這樣用:window.Next.componentName}],"components": []}
- 輔助線的實現(xiàn)(測距、對齊、吸附):需要遍歷所有物體拿到對應的 x、y 值,只不過在遍歷較多組件的時候可以有些策略,比如是否在可視區(qū)、和目標元素的距離、超過十條或者 5ms 就停止遍歷等等
- 版本維護:低碼平臺通常會有 version 字段來維護每個迭代,并且會有好多種 version,包括但不限于:協(xié)議有版本號、依賴有版本號、組件有版本號、每次發(fā)布也有版本號
- 怎么調用接口:通常會有一個單獨的模塊來配置所有接口,然后再在右側的屬性設置面板中就挑選對應的接口即可
- 如何監(jiān)控和埋點:接個監(jiān)控和埋點庫,并在全局暴露方法,方便用戶在右側屬性面板添加自定義事件
- 多人開發(fā)同一個頁面如何解決沖突?假如我們有兩個人在編輯同一個頁面,先后點了保存該怎么處理?其實不管你怎么編輯,最終保存的就只有 json 數(shù)據(jù),那么我們就可以把問題轉換為怎么解決兩個不同 json 的沖突,有兩種方式:
- 在項目生成之初,會建個 gitlab 倉庫,借助 git 實現(xiàn)基于行的文本對比,和我們平時開發(fā)一樣,只不過這里只有一個 json 文件
- 兩個 json 合并有沖突根本原因是某個字段或者某個屬性值變了,所以可以自行實現(xiàn)一套對象合并策略,把 json1 和 json2 的不同之處羅列出來,讓用戶保存的時候手動選擇要保留哪一個
- 怎么調試?如果你是前端或許看看控制臺報錯還能知道是什么問題,但如果你是非研發(fā)同學,那就完全沒招了。對此,就需要低碼平臺開發(fā)一個調試模塊,形如 vue 和 react 的 devtool 和 vConsole 調試面板,當然這對非研發(fā)同學還是不夠友好,我們最好需要將具體的錯誤和建議在頁面上醒目的展示出來
- D2C && AI :低碼平臺需要我們自己搭建頁面,D2C 則又省了一步,直接解析設計稿,經(jīng)過繁瑣的轉換后轉成了我們的 json,然后就直接生成了頁面??尚惺强尚?,現(xiàn)在也有很多這樣的產(chǎn)品,并且結合 AI 能夠為其增色不少。但是吧,我覺得這玩意幾乎維護不下去,不過畫餅需要
小結
如果你能看到這里,那說..明…文章寫的還行,哈哈哈嗝。這里我就簡單的用幾句話對本篇文章進行一個總結:
- 低代碼最重要的就是:方向
- 低代碼的基石就是:協(xié)議
- 低代碼的最大的優(yōu)勢:可視和高效
- 低代碼所有的操作都是在操作一個 json
最后的最后,我又重新畫了張圖方便大家記憶:
好啦,本次分享就到這里,有什么問題歡迎點贊評論留言,我們下期再見,拜拜
作者:尤水就下
鏈接:https://juejin.cn/post/7276837017231835136