我所在的業(yè)務(wù)組陕见,負責(zé)公司的多個類目(這些類目就如京東上不同的品類)的產(chǎn)品開發(fā)扒俯。這些不同類目的門店有不同的特定,要展示不同的信息甥啄,因此就要求錄入的東西是不同存炮。但是顯然,我們不打算每一個類目都開發(fā)一遍蜈漓,所以我們設(shè)計了一套可配置頁面解決方案和一套動態(tài)解析數(shù)據(jù)解決方案穆桂。這篇博客講述的是我們的可配置頁面解決方案。不過這個解決方案限制在APP的h5頁面上融虽。
不過擔心泄露公司機密(然而并沒有)享完,所以這里將會忽略一些業(yè)務(wù)相關(guān)的東西。而且舉的例子也和我所在的公司無關(guān)有额。希望讀者見諒(不服你來打我鞍阌帧)。
在可配置頁面里面巍佑,我認為要解決的是三個核心問題:
- 頁面布局和樣式
- 頁面內(nèi)容——也就是數(shù)據(jù)
- 頁面的交互
下面將從這三個角度來探討茴迁。
需求特征和一些概念
首先來看一張圖:
這是我在淘寶上隨便截的圖,雖然它和我實際要解決的需求的圖看起來差別很大萤衰。我給它劃分成了不同的塊笋熬,并且標號了。整個圖腻菇,被稱為頁面胳螟。里面一個個標號的昔馋,被稱為組件(Component)。實際上糖耸,頁面也是一個組件秘遏,被稱為頂級組件,也被稱為根組件(root component)嘉竟。這里的劃分標號并不完整邦危,但是可以看出來一些:
- 整個頁面被認為是一個樹形結(jié)構(gòu),由不同的組件構(gòu)成舍扰。這種樹形結(jié)構(gòu)的劃分是依賴于個人的理解的倦蚪。從圖里面,我劃分出來的樹形結(jié)構(gòu)大概是1和2是根的子節(jié)點边苹,3和4是2的子節(jié)點陵且,5是4的子節(jié)點;
- 組件由組件構(gòu)成个束,它們是遞歸構(gòu)建的慕购。直到最小的組件,這一類的組件邏輯上將無法再被拆分茬底,這一類的組件被稱為單元(unit)沪悲。注意的是,圖中的3阱表,4殿如,5都不是單元,它們都可以進一步被劃分最爬,這里省略了而已涉馁;
前面我們已經(jīng)得出了三個概念:
- 組件:頁面的組成部分;
- 頁面:組件的一種烂叔。在這次設(shè)計方案里面谨胞,一個頁面對應(yīng)的就是一份配置;
-
單元:邏輯上最細粒度的組件蒜鸡,將不可再被拆分胯努;
它們的關(guān)系如圖:
組件、頁面和單元
現(xiàn)在我們來分析一下這個頁面的特征逢防。首先的是叶沛,這個頁面的布局是簡單的,可以認為是一種網(wǎng)格的布局忘朝,以從上到下灰署,從左到右的順序逐個分布組件。整個頁面首先可以被分成1和2兩個組件,這兩個組件從上到下放好溉箕。組件2里面又被分成了3和4兩個晦墙,它們依舊是從上到下順序排列下來。最后的5肴茄,可以被認為是依次按照一行兩列一直排列下去晌畅。
其次,沒有復(fù)雜的交互寡痰。充其量也就是點點點抗楔,而后可能下面展示的內(nèi)容變了,又或者跳轉(zhuǎn)到了新的頁面拦坠。
實際上“篩選”那里并不符合簡單交互连躏,因為篩選點了之后會彈出一個框,然后再點點點一堆贞滨。應(yīng)該算是比較復(fù)雜的交互了入热。
布局和樣式
布局
首先我們要考慮的就是布局和樣式的問題。前面需求特征里面已經(jīng)說過了疲迂,布局是簡單的才顿,它可以被看成是一種網(wǎng)格:
我們將前面截圖的內(nèi)容去掉莫湘,只留下布局尤蒿,那么就是圖中這個樣子。要定義這么一個東西幅垮,實際上很方便腰池,我們只需要指定一個組件其子元素每一列放置多少個組件就可以了。因為配置里面隱含了總共多少個子元素忙芒,那么就可以計算得到行數(shù)示弓。
還有一種比較復(fù)雜的配置解決方案,就是每一個組件呵萨,指定其寬度奏属。假如說一行放置兩個元素,如果是平分的話潮峦,那么就是每個50%囱皿;如果不是均分,那么可以指定每個組件的寬度忱嘹,可以是60%和40%嘱腥,也可以是其他的分配比例。這種配置方式會帶來布局的靈活性拘悦。但是會帶來配置的復(fù)雜性齿兔。因為每一個組件至少要多一個寬度的屬性。
現(xiàn)在有很多前端的框架是支持柵格系統(tǒng)的,因此在布局方面分苇,其實能夠省事很多添诉。不過,此處有一個地方尤其要慎重考慮:在指定寬度布局的時候医寿,謹慎使用固定長度吻商。這句話的意思是,可以使用百分比的方式來指定一個組件占據(jù)的寬度糟红,但是千萬不能直接指定它該有多寬艾帐,比如指定某個組件寬度是60px。這種方式容易變形盆偿,因為不同手機柒爸,其屏幕寬度是有區(qū)別的。
樣式
我在設(shè)計的時候事扭,秉持了一個原則:絕不配置任何有關(guān)樣式的信息捎稚。我認為布局,如果可以避免的話求橄,都應(yīng)該避免今野,只是實在避免不了而已。
我反對配置樣式的理由是:
- 配置樣式會帶來配置的急劇復(fù)雜化:一個組件的樣式罐农,由很多控制選項条霜,而且不同組件之間還會相互影響;
- 配置樣式導(dǎo)致樣式修改調(diào)整都十分困難:這和第一點是一而二二而一的問題涵亏,配置的復(fù)雜化會影響修改配置的任何一個地方宰睡,組件的相互影響讓你根本不敢修改;
但是气筋,一個組件是不可能沒有樣式的拆内。我的解決方案是,同一種類型的組件宠默,都具有同樣的樣式麸恍。實際上,從配置上來說搀矫,基本不含有任何的樣式信息抹沪,是前端自由決定一個組件究竟長什么樣。
不過艾君,還有一些問題要額外考慮采够。第一個是,如果真的有需求冰垄,比如一份配置蹬癌,在兩個APP上用权她,兩個APP的色調(diào)不同(好吧,說的就是我司逝薪,點評APP和美團APP隅要,一個是橘色的主色調(diào),一個是藍色的主色調(diào))董济,那么究竟該如何解決步清?這個問題可以引申為,對于一份配置虏肾,要渲染成多種風(fēng)格廓啊,該怎么搞?
這個問題還比較好解決封豪。在配置里面指定風(fēng)格谴轮,或者說主題(Theme)。Spring實際上對此有很好的支持吹埠。也就是說第步,在不考慮配置組件的樣式的情況下,可以考慮一份配置作為一個整體缘琅,允許有主題的概念粘都。主題則決定了這份配置將如何會被渲染。實際上刷袍,這已經(jīng)超出了頁面配置的范圍了翩隧。
還有一個比較棘手的問題:有兩個組件,它們除了樣式不同以外做个,都一樣鸽心,那么怎么辦滚局?最為直觀的例子就是居暖,文案一個要加粗,一個不要:
我的解決方案是藤肢,我會定義一種新的組件太闺。這種方法十分粗暴,但是其僅適用于這一類組件不多的情況嘁圈。否則會帶來組件類型膨脹的問題省骂。
還可以考慮另外一個解決方案,這種方案是我事后完成設(shè)計之后才想出來的最住,未曾實踐過钞澳,不知道效果。就是涨缚,雖然我們不配置每個組件的樣式轧粟,但是我們配置每個組件的主題。比如說前面圖中的三個文案,可以指定為三種主題(normal, bold, red)兰吟。這能夠防止組件類型的膨脹通惫,也能避免直接配置樣式帶來的問題。
我覺得混蔼,這種方法應(yīng)該算是最好的了吧履腋。不過我還是覺得,配置不要牽涉樣式是最好的惭嚣。
頁面內(nèi)容——數(shù)據(jù)
這部分要考慮的問題是:什么時候加載數(shù)據(jù)遵湖?是把數(shù)據(jù)糅合進去配置里面,或者說一份配置經(jīng)過轉(zhuǎn)變之后晚吞,也就是填充了數(shù)據(jù)之后奄侠,再交給前端,還是直接返回配置载矿,讓前端再次發(fā)請求垄潮,以獲得數(shù)據(jù)?
對于第一種而言闷盔,很多工作被后端完成了弯洗,這在前端資源緊缺的情況下可以考慮。但是逢勾,更加好的實踐牡整,應(yīng)該是后面一種,尤其是在涉及一些交互的時候——純粹的展示溺拱,對于兩者來說逃贝,區(qū)別并不大。
在下面交互這個部分迫摔,還會有對數(shù)據(jù)的進一步討論沐扳。
頁面交互
交互是整個配置頁面最難解決的地方。對于頁面的一次交互來說句占,有幾個要素:
- 目標:其實就是交互所涉及的組件沪摄;
- 觸發(fā)器:是如何觸發(fā)這一次交互的。比如說可以是點擊了某個東西纱烘,也可以是輸入了某個東西杨拐;
- 數(shù)據(jù):如,點擊一次之后顯示新的內(nèi)容擂啥,那么這個新的內(nèi)容就是數(shù)據(jù)哄陶。又或者在用戶輸入的時候?qū)崟r校驗輸入的格式,那么用戶輸入就是必然要獲取的數(shù)據(jù)
如果寫過前端的讀者哺壶,應(yīng)該很容易就看出屋吨,這個和JS的事件模型的概念是比較接近的舱痘,我只是換了一種說法而已。實際上离赫,這也的確是參考前端事件處理的一般機制來設(shè)計的芭逝。
處理頁面交互,我其實沒有很好的方法渊胸,最根本的問題在于:如果交互涉及業(yè)務(wù)邏輯旬盯,那么就不可能通過配置來完成。
所以我采用了一種十分簡單粗暴的方法翎猛。我預(yù)定義了幾種動作胖翰,這里列舉一部分:
- 第一種動作我稱為提交。顧名思義切厘,這個動作會將表單數(shù)據(jù)取出來一一進行校驗之后萨咳,提交給后端,并簡單提示結(jié)果疫稿;
- 第二種動作我稱為即時輸入校驗培他。即只要是輸入類的組件,比如輸入框遗座,下拉框這種東西舀凛,都會在每次值發(fā)生變化的時候執(zhí)行一次前端校驗,而后在不通過的時候提示結(jié)果途蒋;
- 第三種是級聯(lián)猛遍。比如說,點擊某個地方号坡,然后頁面展示出來的內(nèi)容就會換掉懊烤。這并不需要往后面請求數(shù)據(jù),實際上數(shù)據(jù)都取出來了宽堆,只是控制其中的部分展示與否腌紧;
這三種是我覺得比較常用的。后面又陸續(xù)添加了一些日麸,但是大多數(shù)都圍繞那么一個核心:向后端請求數(shù)據(jù)寄啼,展示某部分數(shù)據(jù)。
所以代箭,實際上,要解決交互涕刚,最為重要的就是要解決數(shù)據(jù)訪問的問題嗡综。
頁面交互與數(shù)據(jù)訪問
頁面交互,常常要面對的一個問題是:如果交互涉及到了數(shù)據(jù)杜漠,那么我該怎么取到這些數(shù)據(jù)呢极景?舉個例子最簡單的例子察净,一些范圍的輸入,比如說價格篩選范圍盼樟,要求第一個價格比第二個價格低氢卡。那么在校驗的時候,就必須要同時知道兩個輸入的內(nèi)容晨缴。
這個價格只是舉例子而已译秦,類似的還有日期輸入。實際上良好的交互設(shè)計击碗,或者系統(tǒng)實現(xiàn)筑悴,應(yīng)該是無論用戶怎么輸入,90稍途,或是09阁吝,或者9~9,系統(tǒng)都能給出正確的結(jié)果械拍。
我們很容易在輸入框上綁定一個監(jiān)聽輸入的事件突勇,也很容易獲得綁定了的那個輸入框的內(nèi)容,但是另外一個輸入框的呢坷虑?解決方案可能是与境,直接操作DOM樹。這種方案是能夠解決問題猖吴,只要你能夠確保擁有一個合理的組件標識符生成策略摔刁,能給予那個輸入框一個在頁面內(nèi)獨一無二的標識符。
但是還有一種更加優(yōu)雅的解決方案海蔽。這是參考當下的一些MVVM框架的實現(xiàn)共屈。可以在全局維護一個數(shù)據(jù)model党窜。上面的例子拗引,要校驗價格是否合理,只需要訪問這個model就可以幌衣,它已經(jīng)不需要關(guān)心這個數(shù)據(jù)究竟是怎么輸入矾削,從哪里輸入的了。因此豁护,擁有這個全局model之后哼凯,所有的數(shù)據(jù)訪問以及變更都是通過這個model來進行的。
這個模型能夠解決所有的跨組件通信的問題(它依然不能解決跨頁面通信問題楚里,不過這種需求幾乎不可能出現(xiàn)在APP上)断部。但是它存在兩個問題沒有解決:
-
作用域隔離。因為我們只有一個全局model班缎,所有的數(shù)據(jù)都往這里塞蝴光。我們可能會面臨這樣一個問題她渴,這個頁面有兩個相同的字段,比如都叫totalPrice蔑祟,但是它出現(xiàn)在不同的地方有不同的含義趁耗。就頁面組織而言,兩個字段可能對應(yīng)兩個屬于不同模塊的組件疆虚。全局model的困難就在于苛败,比較難表達這種區(qū)別辕狰。一種合適的手段是采用合理的層級關(guān)系:
層級關(guān)系的model組織形式
不過刑枝,我有一個更加粗暴,我也覺得是更加好的解決方案衡怀。那就是避免出現(xiàn)這種情況牍帚。確保整個全局model里面儡遮,每一項數(shù)據(jù)都是獨一無二的。一般的應(yīng)用而言暗赶,這點是很容易做到的鄙币;
- 另外一個問題,是安全問題蹂随。數(shù)據(jù)只是放在一個model里面十嘿,自然容易出現(xiàn)非法訪問的問題。不過岳锁,在我看來绩衷,任何放在前端的數(shù)據(jù)都是不安全的數(shù)據(jù),所以激率,這個問題其實解決不解決咳燕,在我眼里相差不多;
回到我前面提到的乒躺,交互較多的時候招盲,由前端分別獲取配置和數(shù)據(jù)的優(yōu)越之處就在于,單獨一次獲取數(shù)據(jù)嘉冒,能夠較容易構(gòu)建這樣的一個model曹货。而如果由后端將配置和數(shù)據(jù)聚合之后,前端就需要更加復(fù)雜的解析配置的工作讳推,以構(gòu)建這個model顶籽。
實際上,這里可以把model看做是一個中間件娜遵,只是借助于這個Model來實現(xiàn)交互中的數(shù)據(jù)傳遞蜕衡。而后,通過監(jiān)聽這個Model中數(shù)據(jù)的變化來實現(xiàn)頁面刷新设拟。這個流程做過前端開發(fā)的讀者應(yīng)該很熟悉——的確慨仿,這就是一般的MVVM框架的工作方式。
注:圖片引自http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html
所以纳胧,如果能夠借助于一些MVVM的框架镰吆,即便不是雙向綁定的框架,單向綁定的框架也能極大的簡化工作量跑慕。
交互例子
我感覺這部分還是要借助一些例子來說明我是怎么設(shè)計整個交互的⊥蛎螅現(xiàn)在假定我們要提交數(shù)據(jù)。前面我已經(jīng)說過核行,所有的輸入數(shù)據(jù)都可以在model里面找到牢硅。這個表單的配置里面已經(jīng)告訴了我需要提交哪些數(shù)據(jù)(暫時忽略校驗過程),并且告訴了我要提交到哪里:
總體流程
經(jīng)過前面的分析芝雪,現(xiàn)在可以給出我們設(shè)計的前端渲染頁面的整個過程:
下面我來分析一下當中某些過程的要點减余。
load module
這個過程是很重要的。我將Module定義為一些組件和預(yù)定義事件的集合惩系。它是用來支持擴展性的一個東西位岔。因為在開發(fā)的過程中,無法預(yù)計到會有多少種組件堡牡,也無法預(yù)計到會有何種交互抒抬。所以關(guān)鍵不在于設(shè)計好組件,而在于設(shè)計好組件接入方式晤柄。而我們自身設(shè)計好的組件擦剑,也不過是利用這種方式接入而已,切記芥颈!如果你能夠通過良好定義接口接入自己預(yù)定義好的組件惠勒,那么別人才有可能通過這個接口接入自己定義的組件。
因此浇借,在允許別人自定義組件的情況下捉撮,就需要有能力從配置中解析出來需要用到的module,并將其加載過來妇垢。整個過程完成后巾遭,那么解析配置所需要的全部材料都已經(jīng)準備好了。
render template 和load data
渲染模板(render template)是一個可選的動作闯估,因為后面可以在加載數(shù)據(jù)之后灼舍,render component的時候完成整個頁面渲染。首先我要解釋一下渲染模板涨薪。這個名詞其實不準確骑素。準確的說法是渲染一切不需要數(shù)據(jù)就能完成的組件。例如刚夺,常見的頁面的頁頭献丑,或者品牌的Logo都是直接可以渲染的末捣,不需要經(jīng)過加載數(shù)據(jù)這么一步。因此可以先完成這一部分的渲染创橄。這樣頁面會立刻顯示出來一部分箩做,避免用戶長時間面對空白頁面。
在這個過程中妥畏,可以并行的是同時加載數(shù)據(jù)邦邦。例如前面截圖淘寶的頁面那樣,去加載商品數(shù)據(jù)醉蚁。完成了數(shù)據(jù)加載之后燃辖,最后進行的就是render component。如果依賴于一些MVVM框架的話网棍,這個過程大概不需要花費什么功夫黔龟。
register event
這是一個我要著重強調(diào)的步驟,注冊事件确沸!所謂的注冊事件捌锭,就是利用已經(jīng)加載數(shù)據(jù)信息,在組件上綁定事件的過程罗捎。這個過程观谦,要放在最后一步的原因是:某些動作是與數(shù)據(jù)相關(guān)的。比如說會員等級不同桨菜,會影響加載的組件豁状。有些會員會出現(xiàn)額外的按鈕,或者有些會員點擊按鈕是跳轉(zhuǎn)倒得,而另外一些會員點擊按鈕卻是提示一個信息泻红。
因此,只有在完成數(shù)據(jù)加載之后霞掺,整個頁面才是完整的谊路。
系統(tǒng)劃分
系統(tǒng)可以如下劃分:
- model模塊:維護的則是全局model。它只應(yīng)該暴露兩個接口菩彬,一個是取數(shù)據(jù)的接口缠劝,一個是更改的接口;
- 事件模塊:它要完成整個事件的執(zhí)行骗灶。實際上惨恭,它依賴于下面的module模塊;
- module模塊:里面維護任何一個組件耙旦,任何一個事件的基本信息脱羡。頁面的渲染和事件的執(zhí)行都依賴于其中的信息;
- 通信模塊:它將負責(zé)和服務(wù)端的數(shù)據(jù)交互;
- 控制模塊:這是一個如果有必要可以進一步劃分的模塊锉罐。它將負責(zé)配置的解析帆竹,確定是否需要加載額外的模塊;在model發(fā)生變化的時候刷新頁面氓鄙;頁面變化的時候?qū)?shù)據(jù)寫入等馆揉。它依賴于其余各個模塊的通力合作业舍;
- 預(yù)定義module和第三方module:這就是事實上承擔工作的部分抖拦。我們提供了預(yù)定義的一些組件,比如復(fù)選框舷暮,下拉框等态罪。但是也允許第三方接入自己的module。其實下面,這些預(yù)定義的module也是用同一種方式接入的复颈;
額外考慮的問題
原子組件
這是一個討論拆分粒度的問題。舉個例子來說:
這個從最細粒度上來說耗啦,大概可以拆成三個部分:
- 返回的那個箭頭
- 輸入框
- 照相機圖標
然而實際上,考慮到實際的應(yīng)用場景机杜,這三個經(jīng)常被合在一起使用帜讲,那么完全可以將它作為一個最基本的不可再拆分的原子組件了。這樣做能夠省去很多的配置椒拗。就復(fù)用性而言似将,也還是能夠接受了。在大多數(shù)的頁頭蚀苛,都能用上在验。
我這里可以額外討論一下粒度的問題。組件粒度(或者是模塊粒度堵未,業(yè)務(wù)粒度)和可用性腋舌、復(fù)用性是息息相關(guān)的。一般而言渗蟹,它們的關(guān)系都是粒度越細块饺,可用性越差,復(fù)用性越強拙徽;反之則是可用性越強刨沦,復(fù)用性越差。還有一個相關(guān)的是復(fù)雜度膘怕。粒度越細想诅,那么組件內(nèi)部復(fù)雜性就會低,而組件之間合作的話,復(fù)雜度就會上升来破。
如前例子徘禁。如果拆成三個诅诱,那么輸入框是可以做成通用的輸入框組件。但是可用性會變低送朱,因為為了配置這個頁頭娘荡,不得不指明這三個組件,以及他們的分布和一些提示文案驶沼,這也是復(fù)雜度顯著上升的表現(xiàn)炮沐。
在拆分組件的時候,要注意權(quán)衡復(fù)用性回怜、可用性和粒度大年。
數(shù)據(jù)校驗
如果配置的頁面,是允許用戶輸入的玉雾,那么數(shù)據(jù)校驗就是一個很重要的點了翔试。很不幸的是,我要解決的問題复旬,其實就包含了大量的用戶輸入垦缅。
數(shù)據(jù)校驗要解決的一個問題是,保證后端服務(wù)器校驗邏輯和前端校驗邏輯是一致的赢底。這是一個看起來容易失都,但是里面有比較多坑的問題。
首先是幸冻,因為頁面是配置的粹庞,所以校驗方式也必然是配置的。前端的校驗邏輯是從配置里面讀取的洽损,而要保證后端校驗邏輯和前端校驗邏輯是一致的庞溜,那么豈不是后端也要去解析這份配置了?解決方案的確差不多碑定,但不是后端讀取前端配置流码,而是兩者都從一個公共的地方讀取這種校驗邏輯。
注意的是延刘,我設(shè)計的系統(tǒng)里面漫试,除了這個可配置的前端頁面部分以外,還有一個動態(tài)解析數(shù)據(jù)后端部分與之配合碘赖。而這兩者都是一種面向元數(shù)據(jù)的模式驾荣。因此外构,兩者從同一個地方讀取校驗邏輯是合理的。
我可以再說一下我們定義的幾種校驗:
- 正則表達式校驗:這是最常用播掷,也是最實用的校驗方式审编。幾乎任何輸入都可以用這個方式校驗,可以用于校驗手機號碼歧匈,網(wǎng)址等垒酬;
- 個數(shù)校驗:校驗復(fù)選框,圖片上傳數(shù)量等件炉;
- 范圍和長度校驗:這兩種校驗都可以算是正則表達式的一個簡化勘究,因為它們本來是可以用正則校驗完成的。比如價格范圍妻率,名字長度等乱顾;
還有一種在前端實現(xiàn)起來困難重重的校驗方式,我們稱為級聯(lián)校驗宫静。就是,要確保多個輸入數(shù)據(jù)之間滿足一定的關(guān)系券时。比如說你選中了某個下拉框孤里,比如說衣服,下面它就要求你必須輸入衣服的尺寸橘洞,顏色捌袜,價格范圍。而且炸枣,如果下面的尺寸虏等、顏色或者價格輸入了,也要確保下拉框選中了衣服适肠。這種校驗霍衫,我們采取的方案是——不校驗,而交給后端來校驗侯养。因為前端要配置這種校驗關(guān)系十分困難敦跌,但是交給后端就好多了。因為逛揩,無論前端是否校驗數(shù)據(jù)柠傍,后端接收到數(shù)據(jù)都必須要校驗。
配置的表達
前面讀者可以看到辩稽,我們是使用json格式作為配置的表達形式的惧笛。JSON格式有很多優(yōu)點:
- 天然的樹形結(jié)構(gòu),能夠完美契合我們對布局的設(shè)計逞泄;
- 前端讀取到配置后患整,直接就能夠使用静檬;
- 足夠簡單
還有一種可以供選擇的格式是xml格式。只是這種格式對前端來說并不太友好并级。當然拂檩,后端配置的時候可以用XML配置,或者別的什么格式來配置嘲碧,而后再經(jīng)過轉(zhuǎn)化稻励,變成一種適合前端使用的格式。
配置存儲
配置存儲是一個挺麻煩的問題愈涩。單純的配置望抽,是可以用文件來存儲的。但是如果要配置的數(shù)量很多的話履婉,那么用文件來存放煤篙,會導(dǎo)致文件特別多,管理也麻煩毁腿。
一種方式是直接把配置作為一個整體存到數(shù)據(jù)庫的某個表的某個字段里面辑奈。這是一種挺不錯的解決方案,既不會太復(fù)雜已烤,也能夠避開使用的文件的問題鸠窗。
還有一種存儲方式是,直接將配置拆分成組件來進行存儲胯究。也就是說一個組件是一行稍计,而后維護組件之間的父子關(guān)系。這種可以帶來組件的復(fù)用裕循,在維護配置的一致性上比較有意義臣嚣。如果頁面之間有很多組件是需要共用的,那么這種存儲方式會比較合適剥哑。我們設(shè)計的系統(tǒng)第二版就是使用這種方式硅则。存儲組件的樹形結(jié)構(gòu)使用的是路徑方式,如/component1/component2/...
大多數(shù)情況下星持,在Path這一個字段上建立一個索引差不多就可以了抢埋。因為組件本身是很少被修改的。
限制
- 不支持復(fù)雜布局督暂。這種配置的頁面揪垄,不支持復(fù)雜的布局,它只能使用在布局極為有規(guī)律的地方逻翁。因為復(fù)雜的布局意味著復(fù)雜的配置饥努,而復(fù)雜的配置——為什么不直接開發(fā)呢?
- 不支持復(fù)雜交互八回。
其實在不限制配置文件復(fù)雜度的情況下酷愧, 這些限制都可以被克服驾诈。這是因為,我們可以將頁面的配置看成是頁面的另外一種形式化的描述溶浴。我們常見的對頁面的描述就是HTML標簽和CSS樣式乍迄,這里不過是改為了JSON格式。前端開發(fā)的東西士败,不過將這種JSON配置轉(zhuǎn)化為HTML和CSS形式的描述:
注:我甚至想過一個更加復(fù)雜的解決方案闯两,就是我們定義一套新的語言用于配置,而后再編寫一個編譯期谅将,將這個配置語言編譯成HTML和CSS漾狼,還有JS。也就是我們認為配置一份源代碼饥臂,而HTML和CSS是編譯期輸出的結(jié)果(可以認為是編譯過程中的中間結(jié)果)逊躁。不過這個方案不具有什么可行性,太過于困難隅熙,而且收益也有點低稽煤。所以,將配置看成是另外一種形式的頁面的描述猛们,是可配置頁面最為核心的概念了念脯。
最后
你說的這個系統(tǒng)那么多缺點,要來何用弯淘?
我一再聲明的一個觀點是:我并不想開發(fā)一個一勞永逸的系統(tǒng)。這個系統(tǒng)也并不打算提供各種面面俱到的能力吉懊,我對它的期望是能夠解決八成問題就可以了庐橙。我一直篤信的是,如果依賴于系統(tǒng)解決剩下兩成問題所需要花費的精力借嗽,與解決這八成所花費的精力态鳖,有過之而無不及。
最直觀的是恶导,考到80分很容易浆竭,80-90就有點難了,90-95更難惨寿。而100邦泄,那就基本上是需要上天眷顧了。
要不要開發(fā)一個可拖拽的系統(tǒng)用于配置頁面裂垦?
當且僅當顺囊,有很多非專業(yè)人士需要配置界面的時候,才有必要開發(fā)這么一個東西蕉拢。實踐證明特碳,這種東西的學(xué)習(xí)成本和惡心程度诚亚,絕對是你不想碰的。
PC上能使用嗎午乓?
答案是站宗,能!實際上我第一次設(shè)計這種東西益愈,是外調(diào)到另外一個組支援一個項目的梢灭,當時設(shè)計的是一個PC版的可配置頁面解決方案。在PC上腕唧,要額外考慮的東西更加多或辖,我舉一些例子:
- 布局更加復(fù)雜:APP的布局是簡單,因為屏幕寬度及其有限枣接,可以說整個大小也有限颂暇,由此帶來了很大的布局便利。很不幸的是但惶,這些在PC上都不再具有了耳鸯;
- 元素更加復(fù)雜:在PC上,有什么iframe, modal之類的東西膀曾。也就是會有各色各樣的嵌入頁面县爬,彈窗之類的內(nèi)容;導(dǎo)航也有千萬種添谊,什么下拉框财喳,面包屑……這些設(shè)計的東西真是多不勝數(shù)。如果不限制前端可以使用的元素種類斩狱,那么這個可配置頁面的解決方案耳高,怕是要難產(chǎn)了;
- 交互復(fù)雜:如果說APP還可能受制于屏幕和交互方式所踊,導(dǎo)致交互可以比較簡單泌枪,那么PC上就毫無顧忌了。首先就是在PC上可以輸入亂七八糟的東西——這也會帶來前端輸入校驗的痛苦秕岛,而后PC彈窗的嵌套碌燕,頁面的嵌套也是難點。最坑爹的是跨頁面通信(我就遇到了继薛,還能比這更坑的嗎修壕?)
講了這么多難點,我能夠提供的一點建議就是:牢牢記住頁面就是一個頂級組件惋增。因此頁面的嵌套的可以看成是組件的嵌套叠殷,跨頁面通信可以看成是組件間通信(當然要比一般的組件間通信困難很多,而且限制也更多)诈皿。此外就是林束,禁止產(chǎn)品或者設(shè)計人員自由發(fā)揮O窦!壶冒!
還有就是缕题,設(shè)計要簡單,不要復(fù)雜胖腾。如果因為少數(shù)的幾個特性會導(dǎo)致解決方案復(fù)雜化烟零,那么還是直接開發(fā)頁面吧。
這樣配置有什么好處咸作?
第一個好處是節(jié)省前端資源锨阿。我司前端資源緊缺,這算是設(shè)計系統(tǒng)的一個很重要的初衷了记罚;
第二個好處是節(jié)省測試資源墅诡。因為頁面是配置的(實際上我們的數(shù)據(jù)解析也是配置的),所以后面接入新的業(yè)務(wù)的時候桐智,基本上就是看看配置對不對末早,并不需要測試的介入;
第三個就是吹牛逼说庭。是的然磷,別人問你做了什么,可配置頁面總比一個個頁面開發(fā)過去要牛逼多了刊驴;
免責(zé)聲明
其實我是一個后端開發(fā)姿搜,不過因為一些機緣巧合的東西,所以讓我設(shè)計了這么可配置頁面的解決方案捆憎,頗有一種趕鴨子上架的感覺痪欲。我只能憑借我寥寥幾個月的前端開發(fā)經(jīng)驗,設(shè)計出來了那么一套東西攻礼。雖然我在實際使用中感覺很好用,但是我同事也覺得問題多多栗柒。因此我最后要說的就是礁扮,答案僅供參考。