前端模塊化實踐(方法篇)

從 jQuery 的一統江湖再到 Angular 的異常火爆,咱們能夠看到工程師們對於開發效率孜孜不倦的追求,你們都渴望着可以快速從這個充滿紛爭的互聯網時代脫穎而出。儘管說「天下武功,惟快不破」,可是咱們不應忘記一個事實,那就是一個長期產品的維護成本要遠遠高於開發的成本。相比與單純的代碼量的減小,良好的總體設計更應該足夠簡單,容易被他人徹底正確的理解、能夠快速的定位和處理bug、可以輕易的改變結構或者添加功能、支持輕鬆的編寫測試。css

下文中我會以 Component 爲例,來說講咱們是如何基於 KISS原則 來實現總體的前端模塊化設計(如下簡稱前端模塊化設計),從而幫助產品更好的應對未來代碼維護和升級須要的。html

先來明確下前端模塊化這個概念,以及它所針對的問題。前端

什麼是前端模塊化設計

  • 它所針對的不只僅是經常使用前端小組件的設計,而是整個產品的全部前端部分的所有模塊化。git

  • 不只僅是JS代碼的模塊化,一個模塊能夠包含js、css、html以及圖片等其它資源。github

  • 不一樣模塊間的依賴和調用關係都應該是清晰簡單的,事件流程也應該是能夠被容易讀懂和理解的。後端

  • 先後端要作到徹底分離,後端再也不提供渲染功能,僅僅作爲數據的提供者(rest接口),不然沒法確保調整的可靠性。瀏覽器

  • 它只針對應用型產品,對於追求性能和 SEO 的展現性頁面來講,前端的功能是有限的,用不着作總體的模塊化設計。mvc

  • 它不是企業級組件,所謂企業級組件通常都是先後端一體的、跟具體某個業務緊密聯繫的,而前端模塊化只包括前端,它只對應一個產品的一個部分,不必定對應某個具體業務(不一樣業務同時依賴一個模塊是很常見的)。app

爲何要作前端模塊化設計

  1. 咱們的產品須要針對不一樣客戶進行界面定製調整,它須要足夠靈活應對各類組件上的變化。框架

  2. 咱們但願咱們的產品可以帶給用戶 app 同樣的流暢訪問體驗,而不是不停的重複刷新頁面。

  3. 咱們的產品須要支持不一樣的設備,咱們但願大部分模塊是可重用的,而沒必要再去從新開發。

  4. 咱們的先後端是分離的,前端額外還承載了全部的渲染、路由、以及組件生命週期管理的邏輯,咱們但願它簡單可靠。

前端模塊化設計的方法

  • MVX模式。咱們的界面只由三種簡單的模塊構成,M是Model,V是View,X就是其它。

    mvx.png

    1. Model,負責定義各類模型,以及向後端發送對應的資源增刪改查請求並完成對應模型的轉化。

    2. View,使用模板並提供渲染方法、綁定對應Model、建立並管理子view、響應各類事件、發送事件,以上只有第一點是必須的。

    3. 其它包含通用的組件,例如:tip、menu、dialog等等,以及各類全局通用的模塊,提供例如錯誤提示、多語言、保存獲取當前用戶信息等功能。

  • 嚴格依賴關係。簡單來講就是層次化,具體包含如下幾點:

    1. 通用組件和全局模塊毫不應當依賴任何Model和View。

    2. Model只能依賴全局模塊和通用組件。

    3. View能夠依賴任何組件,但它毫不能夠依賴除了自身建立的子View以外的任何View組件(保證獨立性)。

    如下是一個真實項目中的模塊依賴關係,能夠看到清晰的層次關係
    out.png
    (其中user、client、board、widget是model)

  • 合理劃分界面模塊。能夠參考 Sencha的這篇文章,簡言之就是不能太大,太大承載的邏輯太多,也不能過小,過小則過於碎片化,增大管理的難度。咱們的作法是先按邏輯上互相獨立的大區域作成最底層的view,邏輯上應當獨立的塊作成獨立的view,對於中間的view層則最開始儘可能少作。若是發現因爲業務的變動中間的view層承接了太多功能再進行拆分,由於view只會被它的上一層建立者調用,並且它依賴的模塊由於使用 CMD 也能很是清楚的看到,因此重構起來仍是很是容易的。

  • 可控的消息機制。父view能夠直接調用子view的方法,可是子view發生變化,須要通知其它的view該怎麼辦呢?咱們不喜歡 pagebus 那種不可控的全局消息模式,因此咱們採用了相似瀏覽器事件冒泡的簡單的事件冒泡方式,並且限制了子view的事件只能由上一層接受,若是還須要向上傳遞則讓上一層繼續發送事件,實踐中這種需求仍是很是少的。

代碼示例

  • 用戶模塊
var model = require('model');

/**
 * User model.
 */

module.exports = model('User')
  .attr('id')
  .attr('name')
  .attr('email')
  • 視圖模塊
var dom = require('dom');
var User = require('user');
var Emitter = require('emitter');
var template = require('./template');

//parent爲渲染該view的dom節點
function UsersView(parent) {
    var el = dom(template);
    el.appendTo(parent);
    this.el = el;
    el.on('click', '.add', this.addUser.bind(this));
    //load all users
    User.all(function(err, users){
       if(err) throw err;
       users.each(function(user){
          this.appendUser(user);
       }.bind(this));
    }.bind(this));
}

Emitter(UsersView.prototype);

UsersView.prototype.addUser = function(){
    var user = new User({
        name: this.el.find('[name="name"]').value(),
        email: this.el.find('[name="email"]').value()
    });
    user.save(function(err, user){
        if(err) throw err;
        this.appendUser(user);
        //向父層發送通知
        this.emit('new', user);
    }.bind(this));
}

UsersView.prototype.appendUser = function(user){
    //渲染新用戶,此處省略
}

module.exports = UsersView;
  • 父視圖模塊
var template = require('./template');
var dom = require('dom');
//模塊名採用小寫加中橫線命名,由於文件夾採用小寫命名更好
var UserView = require('user-view');

function MainView(parent) {
    var el = dom(template);
    el.appendTo(parent);
    this.userView = new UserView(el.get(0));
    //事件接受
    this.userView.on('new', function(user){
        console.log('new user ' + user.name);
    }.bind(this));
}

經過遵循簡單的方法和約定,咱們能夠作到不管是框架總體,仍是具體到某個調用,某個流程都很是容易被開發者所理解,從而極大的提高了後期繼續開發和維護的效率。

下一篇我會談談咱們基於應用模塊化所作的各類約定,它們可讓整個前端體系保持簡單一致,有效的避免衝突,而且幫助咱們快速定位和處理各類問題。

相關文章
相關標籤/搜索