在軟件開發中,研發效率永遠是開發人員不斷追求的主題之一。於公司而言,在競爭激烈的互聯網行業中,產出得快和慢也許就決定着公司的生死存亡;於個人而言,效率高了就可以少加班,多出時間去提升自己、發展愛好、陪伴家人,工作、生活兩不誤。
提升效率的途徑,無外乎就是「方法」和「工具」。以一個開發者的思維來想,就是將工作內容進行總結、歸納,從一組相似的工作內容中提煉共同點,抽象出解決這一類問題的方法,從而造出便於在今後的工作中更為快速解決這類問題的工具。這個「工具」可以是個函數、組件、中間件、插件,也可以是 IDE、其他開發工具的擴展,甚至是語言。
面向組件
在現代前端開發中,如果去問一個業務前端開發:「如何提升團隊開發效率?」對方所回答的內容中,極有可能會出現「組件庫」。沒錯,在前端工程化趨近完善的今天,在近幾年 React、Vue 等組件化庫/框架的影響下,面向組件開發的思維方式早已深入人心。
組件庫提效有限
現在,組件庫已經是一個前端團隊的必備設施了,長遠來看,團隊一定且必須要有自己的組件庫。開源的第三方組件庫再好,對於一家企業的前端團隊來説也只是短期用來充飢的,因為它們無法完全滿足一家公司的業務場景,並且出於多終端支持的考慮,必定要進行二次開發或者自研。
組件庫有了,團隊和公司中推廣的效果也不錯,絕大多數的人都在用。使用組件開發頁面相對 jQuery 時代要每塊功能區都得從 <span>、<div> 等 HTML 標籤碼起來説確實提升了效率,然而有限;要搞出頁面需要反覆去引入組件,然後組合拼裝出來,就像工廠流水線上的工人拼裝零件,仍然要去做很多重複動作。
只要覺得當前的開發方式重複的動作多了,就代表還能繼續提效,得想個法子減少重複無意義動作。
面向組件的開發方式,是現代前端頁面開發提效的初級階段,也是一個團隊所要必經的階段。
更高層面的提效
在之前寫的文章中有段話——
組件可以很簡單,也可以很複雜。按照複雜程度從小到大排的話,可以分為幾類:
- 基礎組件;
- 複合組件;
- 頁面;
- 應用。
對,不用揉眼睛,你沒有看錯!
站在更高的角度去看,「頁面」和「應用」也是一種「組件」,只不過它們更為複雜。在這裏我想要説的不是它們,而是「基礎組件」和「複合組件」。
歐雷《我來聊聊面向組件的前端開發》
文中提到了「頁面」和「應用」也可以看作是種「組件」。雖然與當時的想法有些差異,但本文的內容就是要在那篇文章的基礎上簡單聊聊在「頁面」層面的提效。
一般來説,「頁面」是用户所能看到的最大、最完整的界面,如果能在這個層面有個很好的抽象方案,在做業務開發時與單純地面向組件開發相比,應該會有更大的提效效果。
GUI 發展了幾十年,人機交互的圖形元素及佈局方式已經相對固定,只要不是出現像 Google Glass 之類的革命性交互設備,就不會發生重大改變。在業務開發中界面形式更是千篇一律,尤其是 web 頁面,尤其是中後台系統的 web 頁面,一定可以通過什麼方式來將這種「千篇一律」進行抽象。
試着來回想下,自己所做過的中後台系統的絕大部分頁面是不是我所描述的這樣——
頁面整體是上下或左右佈局。如果是上下佈局的話,上面是頁頭,下面的左側可能有帶頁面導航的側邊欄,或者沒有側邊欄直接將頁面導航全部集中在頁頭中,剩餘區域是頁面主體部分,承載着這個頁面的主要數據和功能;如果是左右佈局,左側毋庸置疑就是有頁面導航的側邊欄,頁頭跑到了右側上面,其餘是頁面主體。
中後台系統的主要功能就是 CRUD,即業務數據的增刪改查,相對應的頁面展現及交互形式就是列表頁、表單頁和詳情頁。列表頁彙總了所有業務數據的簡要信息,並提供了數據的增、刪、改和更多信息查看的入口;表單頁肩負着數據新增和修改的功能;詳情頁能夠看到一條業務數據記錄最完整的信息。
每新增一個業務模塊,就要又寫一遍列表頁、表單頁和詳情頁……反覆做這種事情有啥意思呢?既然這三種頁面會反覆出現,那乾脆封裝幾個頁面級別的組件好了,有新需求的時候就建幾個頁面入口文件,裏面分別引入相應的頁面組件,傳入一些 props,完活兒!
這種方式看起來不錯,然而存在幾個問題:
- 沒有描述出頁面內容的結構,已封裝好的頁面組件對於使用者來説算是個黑盒子,頁面內容是什麼結構不去看源碼不得而知;
- 如果新需求中雖然需要列表頁、表單頁和詳情頁,但與已封裝好的能夠覆蓋大部分場景的相關組件所支持的頁面有些差異,擴展性是個問題;
- 每來新需求就要新建頁面入口文件然後在裏面引入頁面組件,還是會有很多無意義重複動作和重複代碼,時間長了還是覺得煩。
我需要一種既能看一眼就理解內容結構和關係,又具備較好擴展性,還能減少重複代碼和無意義動作的方式——是的,兜了一個大圈子終於要進入正題了——面向模板開發。
面向模板
面向模板的前端開發有三大要素:模板;節點;部件。
富有表達力的模板
我所説的「模板」的主要作用是內容結構的描述以及頁面的配置,觀感上與 XHTML 相近。它主要具備以下幾個特徵:
- 字符全部小寫,多單詞用連接符「-」連接,無子孫的標籤直接閉合;
- 包含極少的具備抽象語義的標籤的標籤集;
- 以特定標籤的特定屬性的形式支持有限的輕邏輯。
為什麼不選擇用 JSON 或 JSX 來描述和配置頁面?因為模板更符合直覺,更易讀,並且中立。用模板的話,一眼就能幾乎不用思考地看出都有啥,以及層級關係;如果是 JSON 或 JSX,還得在腦中進行轉換,增加心智負擔,並且拼寫起來相對複雜。Vue 上手如此「簡單」的原因之一,就是它「符合直覺」的設計。
要使用模板去描述頁面的話,就得自定義一套具有抽象語義的標籤集。
頁面的整體佈局可以用如下模板結構去描述:
<layout>
<header>
<title>歐雷流</title>
<navs />
</header>
<layout>
<sidebar>
<navs />
</sidebar>
<content>...</content>
</layout>
<footer>...</footer>
</layout>
看起來是不是跟 HTML 標籤很像?但它們並不是 HTML 標籤,也不會進行渲染,只是用來描述頁面的一段文本。
整體佈局可以描述了,但承載整個頁面的主要數據和功能的主體部分該如何去描述呢?
在上文中提到,我們習慣將中後台系統中與數據的增刪改查相對應的頁面稱為「列表頁」、「表單頁」和「詳情頁」。雖然它們中都帶有「頁」,但真正有區別的只是整個頁面中的一部分區域,通常是頁面主體部分。它們可以被分別看成是一種視圖形式,所以可以將稱呼稍微改變一下——「列表視圖」、「表單視圖」和「詳情視圖」。一般情況下,表單視圖和詳情視圖長得基本一樣,就是一個能編輯一個不能,可以將它們合稱為「表單/詳情視圖」。
「視圖」只描述了一個數據的集合該展示成啥樣,並沒有也沒法去描述每個數據是什麼以及長啥樣,需要一個更小粒度的且能夠去描述每個數據單元的概念——「字段」。這樣一來,用來描述數據的概念和模板標籤已經齊活兒了:
<view>
<field name="name" label="姓名" />
<field name="gender" label="性別" />
<field name="age" label="年齡" />
<field name="birthday" label="生日" />
</view>
雖然數據能夠描述了,但還有些欠缺:表單/詳情視圖中想將字段分組展示沒法描述;對數據的操作也沒有描述。為了解決這兩個問題,再引入「分組」和「動作」。這下,表單/詳情視圖的模板看起來會是這樣:
<view>
<group title="基本信息">
<field name="name" label="姓名" />
<field name="gender" label="性別" />
<field name="age" label="年齡" />
<field name="birthday" label="生日" />
</group>
<group title="寵物">
<field name="dogs" label="🐶" />
<field name="cats" label="🐱" />
</group>
<action ref="submit" text="提交" />
<action ref="reset" text="重置" />
<action ref="cancel" text="取消" />
</view>
模板很好地解決了內容結構描述和配置的問題,但如何去動態地調整結構和更改配置呢?在平常的業務頁面開發時也許不會太凸顯出問題,但碰到流程表單設計或頁面可視化編輯這種靈活性很高的需求時,問題就會被暴露出來了。
充滿控制力的節點
在這裏,我要將定義好的標籤集所拼成的模板解析成節點樹,通過更改樹的結構和節點的屬性去影響頁面最終的呈現效果。每個節點都會有節點的基本信息、對應標籤的屬性和一些節點操作方法:
{
name: "field",
tag: "field",
attrs: {
name: "name",
label: "姓名"
},
parent: {},
children: [],
remove: function() {},
insert: function() {}
}
在頁面模板化且節點化之後,理想情況下,頁面長啥樣已經不受如 React、Vue 等運行時技術棧的束縛,控制權完全在解析模板所生成的節點樹上,要想改變頁面的視覺效果時只需更改節點即可。
極具表現力的部件
頁面內容的描述通過模板來表達了,頁面內容的控制權集中到節點樹中了,那麼頁面內容的呈現在這種體系下應該如何去做呢?負責這塊的,就是接下來要説的面向模板開發的第三大要素——部件。
「部件」這個詞不新鮮,但在我所説的這個面向模板開發的體系中的含義,需要被重新定義一下:「部件」是一個可複用的,顯示的信息排列可由用户改變的,可以進行交互的 GUI 元素。
在這個面向模板開發的體系中,模板和節點樹完全是中立的,即不受運行時的技術棧所影響;而部件是建立在運行時技術棧的基礎之上,但不必限於同一個技術棧。也就是説,可以使用 React 組件,也可以用 Vue 組件。
每個部件在使用前都需要註冊,然後在模板中通過 widget 屬性引用:
<view widget="form">
<group title="基本信息" widget="fieldset">
<field name="name" label="姓名" widget="input" />
<field name="gender" label="性別" widget="radio" />
<field name="age" label="年齡" widget="number" />
<field name="birthday" label="生日" widget="date-picker" />
</group>
<group title="寵物" widget="fieldset">
<field name="dogs" label="🐶" widget="select" />
<field name="cats" label="🐱" widget="select" />
</group>
<action ref="submit" text="提交" widget="button" />
<action ref="reset" text="重置" widget="button" />
<action ref="cancel" text="取消" widget="button" />
</view>
這樣,一個面向模板開發的普通表單頁出來了!
思想總結
面向模板的開發方式很好,能夠大幅度提高業務前端開發效率,一定程度上減少了業務系統的搭建速度;作為核心的模板和節點樹是保持中立的,大大降低了運行時技術棧的遷移成本,且能夠應對多端等場景。
面向模板的開發方式初期投入成本很高,標籤集、模板解析和部件註冊與調用機制等的設計和實現需要較多時間,並且這僅僅是視圖層,邏輯層也需要做出相應的變化,不能簡單地用 props 和事件綁定進行處理了。
這個體系建成之後,在業務開發上會很簡單,但機制理解上會增加部分開發人員的心智負擔。
為了效率,一家公司裏的業務前端開發到最後一定是面向模板,而非面向組件。
本文其他閲讀地址:個人網站|微信公眾號