歡迎光臨
每天分享高質量文章

前端元件化開發實踐

作者:美團技術部落格 – spring

網址:http://tech.meituan.com/frontend-component-practice.html


前言

一位計算機前輩曾說過:

Controlling complexity is the essence of computer programming

隨著前端開發複雜度的日益提升,元件化開發應運而生,並隨著 FIS、React 等優秀框架的出現遍地開花。這一過程同樣發生在美團,面臨業務規模的快速發展和工程師團隊的不斷擴張,我們歷經引入元件化解決資源整合問題、逐步增強元件功能促進開發效率、重新打造新一代元件化方案適應全棧開發和共享共建等階段,努力“controlling complexity”。本文將介紹我們元件化開發的實踐過程。

元件化 1.0:資源重組

在美團早期,前端資源是按照頁面或者類似業務頁面集合的形式進行組織的。例如 order.js 對應訂單相關頁面的互動,account.css 對應賬戶相關頁面的樣式。這種方式在過去的較長一段時間內,持續支撐了整個專案的正常推進,功勛卓著。

隨著業務規模的增加和開發團隊的擴張,這套機制逐漸顯示出它的一些不足:

資源冗餘

頁面的逐漸增加,互動的逐漸複雜化,導致對應的 css 和 js 都有大幅度增長,進而出現為了依賴某個 js 中的一個函式,需要載入整個模組,或者為了使用某個 css 中的部分樣式依賴整個 css,冗餘資源較多

對應關係不直觀

沒有顯而易見的對應規則,導致的一個問題是修改某個業務模組的 css 或者 js 時,幾乎只能依靠 grep。靠人來維護頁面模組 html、css 和 js 之間的依賴關係,容易犯錯,常常出現內容已經刪除但是 css 或 js 還存在的問題

難於單元測試

以頁面為最小粒度進行資源整合,不同功能的業務模組相互影響,複雜度太高,自動化測試難以推進

2013 年開始,在調研了 FIS、BEM 等方案之後,結合美團開發框架的實際,我們初步實現了一套輕量級的元件化開發方案。主要的改進是:

  • 以頁面功能元件為單位聚合前端資源
  • 自動載入符合約定的 css、js 資源
  • 將業務資料到渲染資料的轉換過程獨立出來

舉例來說,美團頂部的搜尋框就被實現為一個元件。

程式碼構成:

www/component/smart-box/

├── smart-box.js # 互動

├── smart-box.php # 渲染資料生產、元件配置

├── smart-box.scss # 樣式

├── smart-box.tpl # 內容

└── test

├── default.js # 自動化測試

└── default.php # 單測頁面

呼叫元件變得十足簡單:

echo View::useComponent(‘smart-box’, [

‘keyword’ => $keyword

]);

對比之前,可以看到元件化的一些特點:

  • 按需載入

只加載必要的前端資源

  • 對應關係非常清晰

元件所需要的前端資源都在同一目錄,職責明確且唯一,對應關係顯著

  • 易於測試

元件是具備獨立展現和互動的最小單元,可利用 Phantom 等工具自動化測試

此外,由於前端資源集中進行排程,元件化也為高階效能最佳化提供了空間。例如實現元件級別的 BigRender、透過資料分析進行資源的合併載入等等。

元件化 2.0:趨於成熟

元件化 1.0 上線後,由於簡單易用,很快得到工程師的認可,並開始在各項業務中應用起來。新的需求接踵而來,一直持續到 2014 年底,這個階段我們稱之為元件化 2.0。下麵介紹下主要的幾個改進。

Lifecycle

元件在高內聚的同時,往往需要暴露一些介面供外界呼叫,從而能夠適應複雜的頁面需求,例如提交訂單頁面需要在支付密碼元件啟動完成後系結提交時的檢查。Web Components、React 等都選擇了生命週期事件/方法,我們也是一樣。

元件的生命週期:

一個元件的完整生命週期包括:

  • init,初始化元件根節點和配置
  • fetch,載入 css 和 js 資源
  • render,內容渲染,預設的渲染內容方式是 BigRender
  • ready,進行資料系結等操作
  • update,資料更新
  • destroy,解除所有事件監聽,刪除所有元件節點

元件提供 pause、resume 方法以方便進行生命週期控制。各個階段使用 Promise 序列進行,非同步的管理更清晰。使用自定義語意事件,在修改預設行為、元件間通訊上充分利用了 YUI 強大的自定義事件體系,有效降低了開發維護成本。

舉個例子,頁面初始化時元件的啟動過程實際也是藉助生命週期實現的:

var afterLoadList = [];

Y.all(‘[data-component]’).each(function (node) {

var component = new Y.mt.Component(node);

// 系結 init 生命週期事件,在 init 預設行為完成後執行回呼

component.after(‘init’, function (e) {

// 如果配置了延遲啟動

if (e.config.afterLoad) {

// 暫停元件生命週期

e.component.pause();

// 壓入延遲啟動陣列

afterLoadList.push(e.component);

}

});

// 開始進入生命週期

component.start();

});

Y.on(‘load’, function () {

// 在頁面 load 事件發生時恢復元件生命週期

afterLoadList.forEach(function (component) {

component.resume();

});

});

回過頭來看,引入生命週期除了帶來擴充套件性外,更重要的是理順了元件的各個階段,有助於更好的理解和運用。

Data Binding

資料系結是我們期盼已久的功能,將 View 和 ViewModel 之間的互動自動化無疑會節省工程師的大量時間。在元件化減少關註點和降低複雜度後,實現資料系結變得更加可能。

我們最終實現的資料系結方案主要參考了 Angular,透過在 html 節點上新增特定的屬性宣告系結邏輯,js 掃描這些內容併進行相應的渲染和事件系結。當資料發生變化時,對應的內容全部重新渲染。

  • mt-bind-repeat="addr in addrList"mt-bind-html="addr.text">

贊(0)

分享創造快樂