我們為什麼要寫元件呢?這裡不細分元件、外掛、控制元件,追究其原因無非讓程式碼,能夠複用,追求更快的開發效率。其實還有個重要的原因,專案大了之後,難以維護。這個時候就會把專案中重複的部分抽取出來,形成一個元件。但是元件也會有些’缺點’,這個最後講。
元件需求
要實現如圖的一個條件選擇器
有的時候,專案時間緊張,就會直接切圖,通過jquery的dom選擇器實現這個’簡單的功能’。
需求分析
為了更好的維護,以及更好的複用此元件,就要做些抽象。
- 資料層:用來決定按鈕個數以及按鈕是否選擇
- 表現層:按鈕使用現有的ui元件
- 邏輯層:按鈕事件等邏輯處理
資料層
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
data: null, choseT: 0, choseF: 0, getDataStatistics: function() { var list = this.data; var choseT = 0; var choseF = 0; var len = list.length; for (var i = 0; i < list.length; i++) { if(list[i].checked == 'checked') { choseT++; }else { choseF++; } } return { choseT: choseT, choseF: choseF, len: len }; }, dataChangeAll: function(checked) { var list = this.data; var len = list.length; checked = checked || ''; if(checked == 'checked') { this.choseT = len; this.choseF = 0; }else { this.choseT = 0; this.choseF = len; } for (var i = 0; i < len; i++) { list[i].checked = checked; } }, dataChangeSingle: function(index, checked) { var list = this.data; var choseT = this.choseT; var choseF = this.choseF; if(checked == 'checked') { choseT++; choseF--; }else { choseT--; choseF++; } this.choseT = choseT; this.choseF = choseF; list[index].checked = checked; } |
資料層主要對原始資料做些CURD的一些操作,具體的操作看具體的業務需求,但是要具有這個意識。
表現層
說白了表現層也就是template層或者view層,就是使用者所看到的,一般會用一個比較成熟的ui庫,比如bootstrap。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
getHtml: function(list, statistic) { var html = ['<div class="sales-dialog">', '<div>', '<div class="tag-box-chose J_view_checkNav">', this.getChoseNav(statistic), '</div>', '<div class="mt_10 J_view_checkItems">', this.getChoseItem(list), '</div>', '</div>', '</div>'].join(''); return html; }, getChoseNav: function(statistic) { var checkAll = ''; var checkNone = ''; var len = statistic.len; if(statistic.choseT == len) { checkAll = 'checked'; } if(statistic.choseF == len) { checkNone = 'checked'; } return [ '<label class="tag '+checkAll+'">', '<input type="radio" name="radio-grade input-fat" class="J_view_checkAll" '+checkAll+' />', '<span class="tag-tit">全選</span>', '</label>', '<label class="tag '+checkNone+'">', '<input type="radio" name="radio-grade" class="J_view_checkNone" '+checkNone+'/>', '<span class="tag-tit">全不選</span>', '</label>' ].join(''); }, getChoseItem: function(list) { var inputs = ''; var doInputFunc = function(i, detail) { return [ '<div style="display:inline-block;width:150px;">', '<label data-toggle="checkbox" class="checkbox-pretty inline '+detail.checked+'">', '<input type="checkbox" value="'+i+'" class="J_view_checkItem " '+detail.checked+'><span>'+detail.name+'</span>', '</label>', '</div>', ].join(''); }; for (var i = 0; i < list.length; i++) { inputs += doInputFunc(i, list[i]); } return inputs; }, domAction: function($el, type, data) { var html = ''; type = type || 'all'; if(type == 'item') { html = this.getChoseItem(data); $el.find('.J_view_checkItems').html(html); }else if(type == 'all') { html = this.getChoseNav(data); $el.find('.J_view_checkNav').html(html); } } |
眾所周知,template就是根據資料渲染成html,在spa專案尤其重要。
邏輯層
這層主要做 呼叫template方法將資料渲染到頁面上;將頁面上的一些事件結果,對映到資料層。其實現在流行的MVVM模式,就是在邏輯層這裡做了更多的事情,只是開發者們不用去關心細節處理,更專注業務的開發。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
eventBind: function($el) { var _this = this; var $target = ''; // 全選 $el.on('change', '.J_view_checkAll', function() { if(!$(this).parent().hasClass('checked')) { _this.module.dataChangeAll('checked'); _this.view.domAction($el, 'all', _this.module.getDataStatistics()); _this.view.domAction($el, 'item', _this.module.data); } }); // 全不選 $el.on('change', '.J_view_checkNone', function() { if(!$(this).parent().hasClass('checked')) { _this.module.dataChangeAll(''); _this.view.domAction($el, 'all', _this.module.getDataStatistics()); _this.view.domAction($el, 'item', _this.module.data); } }); // 單個 $el.on('click', '.J_view_checkItem', function() { $target = $(this); var index = $target.val(); var checked = ''; if($target.prop('checked')) { checked = 'checked'; } _this.module.dataChangeSingle(index, checked); _this.view.domAction($el, 'all', _this.module.getDataStatistics()); }); }, eventUnbind: function($el) { $el.off('change'); $el.off('click'); }, |
總結
這樣子寫能更好的抽象出公共部分,在其它模組就只要傳入資料就可以了,不用重複拷貝程式碼了。
一開始說到元件會有‘缺點’?尤其是業務元件?
分析
專案版本迭代是一個很正常的事情,第一版的時候,比如這個資料選擇項這個元件,在每個模組都有這樣的需求。但是在下一個版本的時候,產品經理在其中一個模組更改了業務需求,這就導致這個模組的資料選擇項,跟其它模組的資料選擇項不一樣了,但是又有80%甚至90%的相似度,這個時候就非常困擾,到底是重新寫個,還是再對原來的元件,增加個相容配置項?
重新寫會有很多重複的程式碼。新增配置項,又必須保證之前所有的模組都要正確,得必須都驗證過去。有人就會覺得直接去驗證好了啊,但是專案大了之後,一個一個去驗證不是解決問題的辦法。
最終解決
在寫元件的時候,業務邏輯部分,現預留配置項,以便後面業務發生改變,通過配置項來重置。尤其是覺得產品經理會更改頻繁的部分。
實在是覺得更改邏輯較大,那就重新寫個吧,因為一個一個去驗證之前的模組的成本還是很大的。
原文地址 http://tostring.site/2016/07/16/%E6%80%8E%E4%B9%88%E5%86%99%E5%A5%BD%E7%BB%84%E4%BB%B6/
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式