從 jQuery 的一統江湖再到 Angular 的異常火爆,咱們能夠看到工程師們對於開發效率孜孜不倦的追求,你們都渴望着可以快速從這個充滿紛爭的互聯網時代脫穎而出。儘管說「天下武功,惟快不破」,可是咱們不應忘記一個事實,那就是一個長期產品的維護成本要遠遠高於開發的成本。相比與單純的代碼量的減小,良好的總體設計更應該足夠簡單,容易被他人徹底正確的理解、能夠快速的定位和處理bug、可以輕易的改變結構或者添加功能、支持輕鬆的編寫測試。css
下文中我會以 Component 爲例,來說講咱們是如何基於 KISS原則 來實現總體的前端模塊化設計(如下簡稱前端模塊化設計),從而幫助產品更好的應對未來代碼維護和升級須要的。html
先來明確下前端模塊化這個概念,以及它所針對的問題。前端
它所針對的不只僅是經常使用前端小組件的設計,而是整個產品的全部前端部分的所有模塊化。git
不只僅是JS代碼的模塊化,一個模塊能夠包含js、css、html以及圖片等其它資源。github
不一樣模塊間的依賴和調用關係都應該是清晰簡單的,事件流程也應該是能夠被容易讀懂和理解的。後端
先後端要作到徹底分離,後端再也不提供渲染功能,僅僅作爲數據的提供者(rest接口),不然沒法確保調整的可靠性。瀏覽器
它只針對應用型產品,對於追求性能和 SEO 的展現性頁面來講,前端的功能是有限的,用不着作總體的模塊化設計。mvc
它不是企業級組件,所謂企業級組件通常都是先後端一體的、跟具體某個業務緊密聯繫的,而前端模塊化只包括前端,它只對應一個產品的一個部分,不必定對應某個具體業務(不一樣業務同時依賴一個模塊是很常見的)。app
咱們的產品須要針對不一樣客戶進行界面定製調整,它須要足夠靈活應對各類組件上的變化。框架
咱們但願咱們的產品可以帶給用戶 app 同樣的流暢訪問體驗,而不是不停的重複刷新頁面。
咱們的產品須要支持不一樣的設備,咱們但願大部分模塊是可重用的,而沒必要再去從新開發。
咱們的先後端是分離的,前端額外還承載了全部的渲染、路由、以及組件生命週期管理的邏輯,咱們但願它簡單可靠。
MVX模式。咱們的界面只由三種簡單的模塊構成,M是Model,V是View,X就是其它。
Model,負責定義各類模型,以及向後端發送對應的資源增刪改查請求並完成對應模型的轉化。
View,使用模板並提供渲染方法、綁定對應Model、建立並管理子view、響應各類事件、發送事件,以上只有第一點是必須的。
其它包含通用的組件,例如:tip、menu、dialog等等,以及各類全局通用的模塊,提供例如錯誤提示、多語言、保存獲取當前用戶信息等功能。
嚴格依賴關係。簡單來講就是層次化,具體包含如下幾點:
通用組件和全局模塊毫不應當依賴任何Model和View。
Model只能依賴全局模塊和通用組件。
View能夠依賴任何組件,但它毫不能夠依賴除了自身建立的子View以外的任何View組件(保證獨立性)。
如下是一個真實項目中的模塊依賴關係,能夠看到清晰的層次關係
(其中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)); }
經過遵循簡單的方法和約定,咱們能夠作到不管是框架總體,仍是具體到某個調用,某個流程都很是容易被開發者所理解,從而極大的提高了後期繼續開發和維護的效率。
下一篇我會談談咱們基於應用模塊化所作的各類約定,它們可讓整個前端體系保持簡單一致,有效的避免衝突,而且幫助咱們快速定位和處理各類問題。