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

前端組件化開發實踐

作者:美團技術博客 – 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)

分享創造快樂