接上文:談談我對前端組件化中「組件」的理解,順帶寫個Vue與React的demojavascript
上次寫完博客後,有朋友反應第一內容有點深,看着迷迷糊糊;第二是感受沒什麼使用場景,太過業務化,還不如直接寫Vue&react的源碼分析,我感受這裏有必要說下個人認識。css
首先,要寫源碼分析很難,第一是他原本就很難,因此通常咱們是想了解他實現的思路而不是代碼;html
第二每一個開發者有本身發風格,因此你要完全讀懂一我的的代碼不容易,除非你是帶着當時做者一樣的問題不斷的尋找解決方案,不斷的重構,纔可能理解用戶的意圖。前端
咱們上一次作的事情其實就是根據本身實際的工做經驗作了和外面框架相似的事情,雖然代碼的健壯、優雅程度跟不上,但卻和其它做者同樣爲解決一樣的問題思考得出的方案,上次作的太晚了,後面就草草結束,事實上在我Demo過程當中發現一個事實:業務代碼都是差很少的,只是一些細節點不同,因此決定產品質量的依舊是開發者的業務代碼能力,框架只是助力而已。java
不能瞭解做者的意圖,不能提升自己的編程水平,就算用上了React&Vue這類組件化的框架,也組織很差代碼;事實上這類代碼由於是面向大型應用的,反而更考驗一我的的架構能力,因此你們要多注重內在修養的提高哦。react
下面咱們進入今天的正題,這裏依舊提供一些幫助理解的資料:git
代碼地址:https://github.com/yexiaochai/module/github
演示地址:http://yexiaochai.github.io/module/me/index.htmlweb
若是對文中的一些代碼比較疑惑,能夠對比着看看這些文章:面試
【移動前端開發實踐】從無到有(統計、請求、MVC、模塊化)H5開發須知
由於第一個demo是Vue的,React應該也相似,對比以前的代碼發現一個重要差別是:
DOM操做真的徹底沒有了!!!
對,是徹底沒有DOM操做了,這個是很牛逼的一件事情,由於我以爲有兩個地方要擺脫DOM操做很難:
① 個人組件應該放到哪一個容器內,我須要一個定位的元素,好比:
1 this.sortModule = new SortModule({ 2 view: this, 3 selector: '.js_sort_wrapper', 4 sortEntity: this.sortEntity 5 });
明確的告訴了組件所屬的容器
② 我比較疑惑像這類列表類型的事件該如何處理,由於一些必要參數是根據event獲取的,好比:
1 listItemClick: function (e) { 2 var el = $(e.currentTarget); 3 //根據el作一些事情 4 }
關於這個Vue的做者認爲應該將事件處理程序內聯,作顯示聲明:
你可能注意到這種事件監聽的方式違背了傳統理念 「separation of concern」。沒必要擔憂,
由於全部的 Vue.js 事件處理方法和表達式都嚴格綁定在當前視圖的 ViewModel 上,它不會致使任何維護困難。實際上,使用 v-on 有幾個好處:
掃一眼 HTML 模板便能輕鬆定位在 JavaScript 代碼裏對應的方法。
由於你無須在 JavaScript 裏手動綁定事件,你的 ViewModel 代碼能夠是很是純粹的邏輯,和 DOM 徹底解耦,更易於測試。
當一個 ViewModel 被銷燬時,全部的事件處理器都會自動被刪除。你無須擔憂如何本身清理它們。
<button v-on:click="say('hello!', $event)">Submit</button>
1 methods: { 2 say: function (msg, event) { 3 // 如今咱們能夠訪問原生事件對象 4 event.preventDefault() 5 } 6 }
還有種經常使用的操做,好比radioList,點擊當前選項便選擇項目,咱們通常的作法是這樣的:
1 setIndex: function (i) { 2 this.index = i; 3 this.$('li').removeClass(this.curClass); 4 this.$('li[data-index="' + i + '"]').addClass(this.curClass); 5 }
這樣作比較簡單,可是會有一個問題,即是數據與dom表現的流程變了,正確的流程是index 變了,dom便根據數據作更新,好比Vue:
1 setIndex: function (i) { 2 this.index = i; 3 //這部分邏輯Vue會自動實現 4 //this.$('li').removeClass(this.curClass); 5 //this.$('li[data-index="' + i + '"]').addClass(this.curClass); 6 }
以前,不考慮性能,咱們會直接根據數據從新渲染整個列表,就爲一個簡單的選中功能,而Vue&React卻作到了局部渲染,這個是否牛逼,我相信這個將會是一個核心算法部分,後面有時間必定要深刻了解。
根據以上局部解讀,咱們獲得一個結論,只要達成兩個條件,就能擺脫DOM操做:
① 知道組件所處容器
② 根據數據渲染頁面
PS:咱們這裏是很簡單的一環,沒有考慮組件嵌套,組件通訊等過於複雜的問題
那麼若是達成了以上條件,咱們可否作到業務邏輯中不包含dom操做呢?咱們下面就來試試。
這裏真的是demo類嘗試,思惟驗證,便不使用以前過於複雜的業務邏輯了,這裏將me目錄拷貝一塊出來,依舊以原來的代碼作底層依賴,只要列表與頂部排序部分功能,這裏爲了簡化實現,保持代碼重用,咱們這裏直接想將entity模塊複用,要求data中的對象必須是一個entity實例,這裏第一步是抽象出來了list module模塊,因而主控制器變成這樣了,事實上這個時候已經沒dom操做了:
1 initEntity: function () { 2 //實例化排序的導航欄的實體 3 this.sortEntity = new SortEntity(); 4 this.sortEntity.subscribe(this.renderList, this); 5 }, 6 7 initModule: function () { 8 //view爲注入給組件的根元素 9 //selector爲組件將要顯示的容器 10 //sortEntity爲注入給組件的數據實體,作通訊用 11 //這個module在數據顯示後會自動展現 12 this.sortModule = new SortModule({ 13 view: this, 14 selector: '.js_sort_wrapper', 15 sortEntity: this.sortEntity 16 }); 17 this.listModule = new ListModule({ 18 view: this, 19 selector: '.js_list_wrapper', 20 entity: this.sortEntity 21 }); 22 }, 23 24 propertys: function ($super) { 25 $super(); 26 27 this.initEntity(); 28 this.initModule(); 29 this.viewId = 'list'; 30 this.template = layoutHtml; 31 this.events = {}; 32 }
這裏簡單看看列表組件的實現,其實就是將原來根View的代碼換個位置:
1 define([ 2 'ModuleView', 3 'pages/list.data', 4 'text!pages/tpl.list.html' 5 6 ], function (ModuleView, 7 listData, 8 tpl) { 9 return _.inherit(ModuleView, { 10 11 //此處如果要使用model,處實例化時候必定要保證entity的存在,若是不存在即是業務BUG 12 initData: function () { 13 14 this.template = tpl; 15 this.entity.subscribe(this.render, this); 16 17 }, 18 19 _timeSort: function (data, sort) { 20 data = _.sortBy(data, function (item) { 21 item = item.from_time.split(':'); 22 item = item[0] + '.' + item[1]; 23 item = parseFloat(item); 24 return item; 25 }); 26 if (sort == 'down') data.reverse(); 27 return data; 28 }, 29 30 _sumTimeSort: function (data, sort) { 31 data = _.sortBy(data, function (item) { 32 return parseInt(item.use_time); 33 }); 34 if (sort == 'down') data.reverse(); 35 return data; 36 }, 37 38 _priceSort: function (data, sort) { 39 data = _.sortBy(data, function (item) { 40 return item.min_price; 41 }); 42 if (sort == 'down') data.reverse(); 43 return data; 44 }, 45 46 //獲取導航欄排序後的數據 47 getSortData: function (data) { 48 var tmp = []; 49 var sort = this.entity.get(); 50 51 for (var k in sort) { 52 if (sort[k].length > 0) { 53 tmp = this['_' + k + 'Sort'](data, sort[k]) 54 return tmp; 55 } 56 } 57 }, 58 59 //複雜的業務數據處理,爲了達到產品的需求,這段代碼邏輯與業務相關 60 //這段數據處理的代碼過長(超過50行就過長),應該重構掉 61 formatData: function (data) { 62 var item, seat; 63 var typeMap = { 64 'g': 'g', 65 'd': 'd', 66 't': 't', 67 'c': 'g' 68 }; 69 70 //出發時間對應的分鐘數 71 var fromMinute = 0; 72 73 //獲取當前班車日期當前的時間戳,這個數據是動態的,這裏寫死了 74 var d = 1464192000000; 75 var date = new Date(); 76 var now = parseInt(date.getTime() / 1000); 77 date.setTime(d); 78 var year = date.getFullYear(); 79 var month = date.getMonth(); 80 var day = date.getDate(); 81 var toBegin; 82 var seatName, seatIndex, iii; 83 84 //處理坐席問題,僅顯示二等座,一等座,特等座 無座 85 // 二等座 一等座 商務座 無座 動臥 特等座 86 var my_seats = {}; 87 var seatSort = ['二等座', '一等座', '硬座', '硬臥', '軟臥', '商務座', '無座', '動臥', '特等座', '軟座']; 88 89 for (var i = 0, len = data.length; i < len; i++) { 90 fromMinute = data[i].from_time.split(':'); 91 fromMinute[0] = fromMinute[0] + ''; 92 fromMinute[1] = fromMinute[1] + ''; 93 if ((fromMinute[0].charAt(0) == '0')) fromMinute[0] = fromMinute[0].charAt(1); 94 if ((fromMinute[1].charAt(0) == '0')) fromMinute[1] = fromMinute[1].charAt(1); 95 date = new Date(year, month, day, fromMinute[0], fromMinute[1], 0); 96 fromMinute = parseInt(date.getTime() / 1000) 97 toBegin = parseInt((fromMinute - now) / 60); 98 99 data[i].toBegin = toBegin; 100 101 //處理車次類型問題 102 data[i].my_train_number = typeMap[data[i].train_number.charAt(0).toLowerCase()] || 'other'; 103 104 seat = data[i].seats; 105 //全部餘票 106 data[i].sum_ticket = 0; 107 //最低價 108 data[i].min_price = null; 109 110 for (var j = 0, len1 = seat.length; j < len1; j++) { 111 if (!data[i].min_price || data[i].min_price > seat[j].seat_price) data[i].min_price = parseFloat(seat[j].seat_price); 112 data[i].sum_ticket += parseInt(seat[j].seat_yupiao); 113 114 //坐席問題若是坐席不包括上中下則去掉 115 seatName = seat[j].seat_name; 116 //去掉上中下 117 seatName = seatName.replace(/上|中|下/g, ''); 118 if (!my_seats[seatName]) { 119 my_seats[seatName] = parseInt(seat[j].seat_yupiao); 120 } else { 121 my_seats[seatName] = my_seats[seatName] + parseInt(seat[j].seat_yupiao); 122 } 123 } 124 //這裏myseat爲對象,須要轉換爲數組 125 //將定製坐席轉爲排序後的數組 126 data[i].my_seats = []; 127 for (iii = 0; iii < seatSort.length; iii++) { 128 if (typeof my_seats[seatSort[iii]] == 'number') data[i].my_seats.push({ 129 name: seatSort[iii], 130 yupiao: my_seats[seatSort[iii]] 131 }); 132 } 133 134 my_seats = {}; 135 } 136 137 return data; 138 }, 139 140 //完成全部的篩選條件,邏輯比較重 141 getViewModel: function () { 142 var data = this.formatData(listData); 143 data = this.getSortData(data); 144 return {data: data}; 145 } 146 147 }); 148 149 });
就這種簡單的改變,貌似便擺脫了DOM操做,頁面全部的狀態事實上是能夠作到由數據控制的,可是這裏沒有造成「標籤化」,彷佛不太好,因而咱們來試試是否能改造爲標籤化的代碼。
咱們這裏的業務代碼(module與entity)沒有什麼須要改動的,這裏主要在底層作改造,這裏在我看來是提供了一種「語法糖」的東西,這裏的具體概念後續閱讀Vue源碼再深刻了解,這裏先照着作,這裏看結果想實現,也是咱們經常使用的一種設計方案,首先咱們的index編程了這個樣子:
1 <article class="cm-page page-list" id="main"> 2 <div class="js_sort_wrapper sort-bar-wrapper"> 3 <mySortBar :entity="sortEntity"></mySortBar> 4 </div> 5 <myList :entity="listEntity" :sort="sort"></myList> 6 </article>
1 (function () { 2 require.config({ 3 paths: { 4 'text': 'libs/require.text', 5 6 'AbstractView': 'js/view', 7 'AbstractEntity': 'js/entity', 8 'ModuleView': 'js/module' 9 } 10 }); 11 12 require(['pages/list.label'], function (List) { 13 var list = new List(); 14 list.show(); 15 }); 16 })();
PS:裏面的js鉤子基本無用了
這裏標籤化帶來的好處是,根View中有一段實例代碼能夠不用與選擇器映射了,好比這個:
1 this.sortModule = new SortModule({ 2 //view: this, 3 //selector: '.js_sort_wrapper', 4 //sortEntity: this.sortEntity 5 });
由於處於組件中,其中所處位置已經定了,view實例或者entity實例所有是跟View顯示注入的,這裏根View中參考Vue的使用,新增一個$components與$entities屬性,而後增長一$watch對象。
你們寫底層框架時,私有屬性或者方法使用_method的方式,若是是要釋放的能夠是$method這種,必定要「特殊化」防止被實例或者繼承覆蓋
1 define([ 2 'AbstractView', 'pages/en.sort', 'pages/mod.sort', 'pages/mod.list' 3 ], function (AbstractView, SortEntity, SortModule, ListModule) { 4 return _.inherit(AbstractView, { 5 propertys: function ($super) { 6 $super(); 7 this.$entities = { 8 sortEntity: SortEntity 9 }; 10 this.$components = { 11 mySortBar: SortModule, 12 listModule: ListModule 13 }; 14 this.$watch = { 15 16 }; 17 this.viewId = 'list'; 18 this.template = layoutHtml; 19 this.events = {}; 20 } 21 }); 22 });
他這種作法,須要組件在顯示後框架底層將剛剛的業務代碼實現,使用組件生成的html代碼將原來標籤的佔位符給替換掉。
這裏在組件也須要明示根View須要注入什麼給本身:
PS:事實上這個能夠不寫,寫了對後續屬性的計算有好處
//記錄須要根View注入的屬性 props:[sortEntity],
PS:底層何時執行替換這個是有必定時機的,咱們這裏暫時放到根View展現後,這裏更好的實現,後續咱們在Vue與React中去找尋
由於咱們這裏是demo類實現,爲下降難度,咱們爲每個組件動態增長一個div包裹層,因而,咱們在跟View中,在View展現後,咱們另外多加一段邏輯:
1 //實例化實體,後面要用 2 this._initEntity(); 3 //新增標籤邏輯 4 this._initComponent();
而後將實體與組件的實例化放到框架底層,這裏實體的實例化比較簡單(若是有特殊數據需求再說,這裏只考慮最簡單狀況):
1 _initEntity: function() { 2 var key, entities = this.$entities; 3 //這裏沒有作特殊化,須要注意 4 for(key in entities) { 5 this[key] = new entities[key](); 6 } 7 },
而實例化組件的工做複雜許多,由於他須要將頁面中的自定義標籤替換掉,還須要完成不少屬性注入操做:
1 _initComponent: function() { 2 var key, components = this.$components; 3 for(key in components) { 4 //這裏實例化的過程有點複雜,首先將頁面的標籤作一個替換 5 var s = '' 6 } 7 },
1 _initComponent: function() { 2 var key, components = this.$components; 3 var el, attributes, attr, param, clazz, i, len, tmp, id, name; 4 5 //這裏實例化的過程有點複雜,首先將頁面的標籤作一個替換 6 for(key in components) { 7 param = {}; 8 clazz = components[key]; 9 //由原型鏈上獲取根元素要注入給子組件的屬性(這個實現好像不太好) 10 attributes = clazz.prototype.props; 11 12 //首先獲取標籤dom元素,由於html是不區分大小寫的,這裏將標籤小寫 13 el = this.$(key.toLowerCase()); 14 if(!el[0]) continue; 15 16 if(attributes) { 17 for (i = 0, len = attributes.length; i < len; i++) { 18 attr = attributes[i]; 19 name = el.attr(':' + attr); 20 param[attr] = this[name] || name; 21 } 22 } 23 24 //建立一個空div去替換原來的標籤 25 id = _.uniqueId('componenent-id-'); 26 tmp = $('<div component-name="' + key + '" id="' + id + '"></div>'); 27 tmp.insertBefore(el); 28 el.remove(); 29 param.selector = '#' + id; 30 param.view = this; 31 this[key] = new components[key](param); 32 } 33 34 },
因而這個標籤便能正常展現了:
1 <article class="cm-page page-list" id="main"> 2 <div class="js_sort_wrapper sort-bar-wrapper"> 3 <mySortBar :entity="sortEntity" :myname="111"></mySortBar> 4 </div> 5 <myList :entity="sortEntity" :sort="sort"></myList> 6 </article>
後面想要把這段代碼去掉也十分輕易,我這裏就不進行了:
1 'click .js_sort_item li ': function (e) { 2 var el = $(e.currentTarget); 3 var sort = el.attr('data-sort'); 4 this.entity['set' + sort](); 5 }
這裏首先根據上次Vue的demo產生了一些思考,而且以簡單的demo驗證了這些思考,樓主在使用過程當中發現Vue不少好的點子,後續應該會深刻研究,而且以實際項目入手,這裏回到今天的正題,咱們使用React實現上次遺留的demo。
在我最初接觸React的時候,React Native還沒出現,因此不少人對React的關注不高,當時作移動端直接放棄了angular,以體量來講,React也不在咱們的考慮範圍內,誰知道野心勃勃的Facebook搞出了React Native,讓React完全的跟着火了一把,事實上只要有能力以JavaScript統一Native UI的公司,這個實現就算換個框架依舊會火,雖然都已經這麼火了,可是React的文檔卻不怎樣,我後續也有試水React Native的興趣,屆時再與各位分享。
PS:根據以前的反饋,此次demo稍微作簡單點,也只包含頂部導航和列表便可:
1 <!doctype html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, minimal-ui"/> 6 <meta content="yes" name="apple-mobile-web-app-capable"/> 7 <meta content="black" name="apple-mobile-web-app-status-bar-style"/> 8 <meta name="format-detection" content="telephone=no"/> 9 <link href="./static/css/global.css" rel="stylesheet" type="text/css"/> 10 <link href="./pages/list.css" rel="stylesheet" type="text/css"/> 11 <title>組件化</title> 12 </head> 13 <body> 14 <div class="cm-header"> 15 <h1 class="cm-page-title js_title"> 組件化Demo </h1> 16 </div> 17 18 <article class="cm-page page-list" id="main"> 19 </article> 20 21 22 <script src="./libs/react-with-addons.js"></script> 23 <script src="./libs/JSXTransformer.js"></script> 24 <script type="text/javascript" src="./pages/list.data.js"></script> 25 <script type="text/javascript" src="./libs/underscore.js"></script> 26 <script type="text/jsx"> 27 28 29 30 var MySortBar = React.createClass({ 31 getInitialState: function() { 32 return { 33 time: 'up', 34 sumTime: '', 35 price: '' 36 }; 37 }, 38 39 resetData: function () { 40 this.setState({ 41 time: '', 42 sumTime: '', 43 price: '' 44 }); 45 }, 46 47 setTime: function () { 48 this._setData('time'); 49 }, 50 51 setSumTime: function () { 52 this._setData('sumTime'); 53 }, 54 55 setPrice: function () { 56 this._setData('price'); 57 }, 58 59 _setData: function (key) { 60 var param = {}; 61 62 //若是設置當前key存在,則反置,不然清空篩選,設置默認值 63 if (this.state[key] != '') { 64 if (this.state[key] == 'up') param[key] = 'down'; 65 else param[key] = 'up'; 66 } else { 67 this.resetData(); 68 param[key] = 'down'; 69 } 70 this.setState(param); 71 }, 72 73 _getClassName: function(icon) { 74 return 'icon-sort ' + icon; 75 }, 76 77 render: function () { 78 return ( 79 <ul className="bus-tabs sort-bar js_sort_item"> 80 <li className="tabs-item" onClick={this.setTime} >出發時間<i className={this._getClassName(this.state.time)}></i></li> 81 <li className="tabs-item" onClick={this.setSumTime} >耗時<i className={this._getClassName(this.state.sumTime)}></i></li> 82 <li className="tabs-item" onClick={this.setPrice} >價格<i className={this._getClassName(this.state.price)}></i></li> 83 </ul> 84 ); 85 } 86 87 }); 88 89 var Seat = React.createClass({ 90 render: function () { 91 var seat = this.props.seat; 92 93 return ( 94 <span >{seat.name}({seat.yupiao }) </span> 95 ); 96 } 97 }); 98 99 var Item = React.createClass({ 100 render: function () { 101 102 var item = this.props.item; 103 var mapping = { 104 'g': '高速', 105 't': '特快', 106 'd': '高速動車', 107 'c': '城際高鐵', 108 'z': '直達' 109 }; 110 111 var seats = item.my_seats.map(function(item){ 112 return <Seat seat={item}/>; 113 }); 114 115 116 return ( 117 <li className="bus-list-item "> 118 <div className="bus-seat"> 119 <span className=" fl">{item.train_number } | {mapping[item.my_train_number] || '其它'} </span> 120 <span className=" fr">{parseInt(item.use_time / 60) + '小時' + item.use_time % 60 + '分'}</span> 121 </div> 122 <div className="detail"> 123 <div className="sub-list set-out"> 124 <span className="bus-go-off">{item.from_time}</span> <span className="start"><span className="icon-circle s-icon1"> 125 </span>{item.from_station }</span> <span className="fr price">¥{item.min_price}起</span> 126 </div> 127 <div className="sub-list"> 128 <span className="bus-arrival-time">{item.to_time}</span> <span className="end"><span className="icon-circle s-icon2"> 129 </span>{item.to_station}</span> <span className="fr ">{item.sum_ticket}張</span> 130 </div> 131 </div> 132 <div className="bus-seats-info" > 133 {seats} 134 </div> 135 </li> 136 ); 137 } 138 }); 139 140 141 var MyList = React.createClass({ 142 143 formatData: function (data) { 144 145 var item, seat; 146 var typeMap = { 147 'g': 'g', 148 'd': 'd', 149 't': 't', 150 'c': 'g' 151 }; 152 153 //出發時間對應的分鐘數 154 var fromMinute = 0; 155 156 //獲取當前班車日期當前的時間戳,這個數據是動態的,這裏寫死了 157 var d = 1464192000000; 158 var date = new Date(); 159 var now = parseInt(date.getTime() / 1000); 160 date.setTime(d); 161 var year = date.getFullYear(); 162 var month = date.getMonth(); 163 var day = date.getDate(); 164 var toBegin; 165 var seatName, seatIndex, iii; 166 167 //處理坐席問題,僅顯示二等座,一等座,特等座 無座 168 // 二等座 一等座 商務座 無座 動臥 特等座 169 var my_seats = {}; 170 var seatSort = ['二等座', '一等座', '硬座', '硬臥', '軟臥', '商務座', '無座', '動臥', '特等座', '軟座']; 171 172 for (var i = 0, len = data.length; i < len; i++) { 173 fromMinute = data[i].from_time.split(':'); 174 fromMinute[0] = fromMinute[0] + ''; 175 fromMinute[1] = fromMinute[1] + ''; 176 if ((fromMinute[0].charAt(0) == '0')) fromMinute[0] = fromMinute[0].charAt(1); 177 if ((fromMinute[1].charAt(0) == '0')) fromMinute[1] = fromMinute[1].charAt(1); 178 date = new Date(year, month, day, fromMinute[0], fromMinute[1], 0); 179 fromMinute = parseInt(date.getTime() / 1000) 180 toBegin = parseInt((fromMinute - now) / 60); 181 182 data[i].toBegin = toBegin; 183 184 //處理車次類型問題 185 data[i].my_train_number = typeMap[data[i].train_number.charAt(0).toLowerCase()] || 'other'; 186 187 seat = data[i].seats; 188 //全部餘票 189 data[i].sum_ticket = 0; 190 //最低價 191 data[i].min_price = null; 192 193 for (var j = 0, len1 = seat.length; j < len1; j++) { 194 if (!data[i].min_price || data[i].min_price > seat[j].seat_price) data[i].min_price = parseFloat(seat[j].seat_price); 195 data[i].sum_ticket += parseInt(seat[j].seat_yupiao); 196 197 //坐席問題若是坐席不包括上中下則去掉 198 seatName = seat[j].seat_name; 199 //去掉上中下 200 seatName = seatName.replace(/上|中|下/g, ''); 201 if (!my_seats[seatName]) { 202 my_seats[seatName] = parseInt(seat[j].seat_yupiao); 203 } else { 204 my_seats[seatName] = my_seats[seatName] + parseInt(seat[j].seat_yupiao); 205 } 206 } 207 //這裏myseat爲對象,須要轉換爲數組 208 //將定製坐席轉爲排序後的數組 209 data[i].my_seats = []; 210 for (iii = 0; iii < seatSort.length; iii++) { 211 if (typeof my_seats[seatSort[iii]] == 'number') data[i].my_seats.push({ 212 name: seatSort[iii], 213 yupiao: my_seats[seatSort[iii]] 214 }); 215 } 216 217 my_seats = {}; 218 } 219 220 return data; 221 }, 222 223 render: function () { 224 225 var main; 226 var data = this.formatData(this.props.data); 227 228 229 main = data.map(function(item) { 230 return <Item item={item}/>; 231 }); 232 233 return ( 234 <ul className="bus-list js_bus_list "> 235 {main} 236 </ul> 237 ); 238 } 239 240 }); 241 242 var data = getListData(); 243 244 React.render( 245 <div> 246 <div className="js_sort_wrapper sort-bar-wrapper"> 247 <MySortBar /> 248 </div> 249 <MyList data={data} /> 250 </div>, 251 document.getElementById('main') 252 ); 253 254 </script> 255 256 257 </body> 258 </html>
他這個語法聽說是讓開發變得更簡單了,我反正是不喜歡,這裏有個很差的地方,以前數據實體所有是在根View上實例化的,而後注入給子View,React這裏屬性徹底獨享了,如今我觸發了狀態的改變,如何通知到list組件從新渲染排序呢?
這裏React子組件之間如何通訊暫沒有研究出來,因此將須要通訊的數據作到了父組件中
1 <!doctype html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, minimal-ui"/> 6 <meta content="yes" name="apple-mobile-web-app-capable"/> 7 <meta content="black" name="apple-mobile-web-app-status-bar-style"/> 8 <meta name="format-detection" content="telephone=no"/> 9 <link href="./static/css/global.css" rel="stylesheet" type="text/css"/> 10 <link href="./pages/list.css" rel="stylesheet" type="text/css"/> 11 <title>組件化</title> 12 </head> 13 <body> 14 <div class="cm-header"> 15 <h1 class="cm-page-title js_title"> 組件化Demo </h1> 16 </div> 17 18 <article class="cm-page page-list" id="main"> 19 </article> 20 21 22 <script src="./libs/react-with-addons.js"></script> 23 <script src="./libs/JSXTransformer.js"></script> 24 <script type="text/javascript" src="./pages/list.data.js"></script> 25 <script type="text/javascript" src="./libs/underscore.js"></script> 26 <script type="text/jsx"> 27 28 29 30 var MySortBar = React.createClass({ 31 _getClassName: function(icon) { 32 return 'icon-sort ' + icon; 33 }, 34 35 render: function () { 36 var state = this.props.state; 37 return ( 38 <ul className="bus-tabs sort-bar js_sort_item"> 39 <li className="tabs-item" onClick={this.props.setTime} >出發時間<i className={this._getClassName(state.time)}></i></li> 40 <li className="tabs-item" onClick={this.props.setSumTime} >耗時<i className={this._getClassName(state.sumTime)}></i></li> 41 <li className="tabs-item" onClick={this.props.setPrice} >價格<i className={this._getClassName(state.price)}></i></li> 42 </ul> 43 ); 44 } 45 46 }); 47 48 var Seat = React.createClass({ 49 render: function () { 50 var seat = this.props.seat; 51 52 return ( 53 <span >{seat.name}({seat.yupiao }) </span> 54 ); 55 } 56 }); 57 58 var Item = React.createClass({ 59 render: function () { 60 61 var item = this.props.item; 62 var mapping = { 63 'g': '高速', 64 't': '特快', 65 'd': '高速動車', 66 'c': '城際高鐵', 67 'z': '直達' 68 }; 69 70 var seats = item.my_seats.map(function(item){ 71 return <Seat seat={item}/>; 72 }); 73 74 75 return ( 76 <li className="bus-list-item "> 77 <div className="bus-seat"> 78 <span className=" fl">{item.train_number } | {mapping[item.my_train_number] || '其它'} </span> 79 <span className=" fr">{parseInt(item.use_time / 60) + '小時' + item.use_time % 60 + '分'}</span> 80 </div> 81 <div className="detail"> 82 <div className="sub-list set-out"> 83 <span className="bus-go-off">{item.from_time}</span> <span className="start"><span className="icon-circle s-icon1"> 84 </span>{item.from_station }</span> <span className="fr price">¥{item.min_price}起</span> 85 </div> 86 <div className="sub-list"> 87 <span className="bus-arrival-time">{item.to_time}</span> <span className="end"><span className="icon-circle s-icon2"> 88 </span>{item.to_station}</span> <span className="fr ">{item.sum_ticket}張</span> 89 </div> 90 </div> 91 <div className="bus-seats-info" > 92 {seats} 93 </div> 94 </li> 95 ); 96 } 97 }); 98 99 var MyList = React.createClass({ 100 101 formatData: function (data) { 102 103 var item, seat; 104 var typeMap = { 105 'g': 'g', 106 'd': 'd', 107 't': 't', 108 'c': 'g' 109 }; 110 111 //出發時間對應的分鐘數 112 var fromMinute = 0; 113 114 //獲取當前班車日期當前的時間戳,這個數據是動態的,這裏寫死了 115 var d = 1464192000000; 116 var date = new Date(); 117 var now = parseInt(date.getTime() / 1000); 118 date.setTime(d); 119 var year = date.getFullYear(); 120 var month = date.getMonth(); 121 var day = date.getDate(); 122 var toBegin; 123 var seatName, seatIndex, iii; 124 125 //處理坐席問題,僅顯示二等座,一等座,特等座 無座 126 // 二等座 一等座 商務座 無座 動臥 特等座 127 var my_seats = {}; 128 var seatSort = ['二等座', '一等座', '硬座', '硬臥', '軟臥', '商務座', '無座', '動臥', '特等座', '軟座']; 129 130 for (var i = 0, len = data.length; i < len; i++) { 131 fromMinute = data[i].from_time.split(':'); 132 fromMinute[0] = fromMinute[0] + ''; 133 fromMinute[1] = fromMinute[1] + ''; 134 if ((fromMinute[0].charAt(0) == '0')) fromMinute[0] = fromMinute[0].charAt(1); 135 if ((fromMinute[1].charAt(0) == '0')) fromMinute[1] = fromMinute[1].charAt(1); 136 date = new Date(year, month, day, fromMinute[0], fromMinute[1], 0); 137 fromMinute = parseInt(date.getTime() / 1000) 138 toBegin = parseInt((fromMinute - now) / 60); 139 140 data[i].toBegin = toBegin; 141 142 //處理車次類型問題 143 data[i].my_train_number = typeMap[data[i].train_number.charAt(0).toLowerCase()] || 'other'; 144 145 seat = data[i].seats; 146 //全部餘票 147 data[i].sum_ticket = 0; 148 //最低價 149 data[i].min_price = null; 150 151 for (var j = 0, len1 = seat.length; j < len1; j++) { 152 if (!data[i].min_price || data[i].min_price > seat[j].seat_price) data[i].min_price = parseFloat(seat[j].seat_price); 153 data[i].sum_ticket += parseInt(seat[j].seat_yupiao); 154 155 //坐席問題若是坐席不包括上中下則去掉 156 seatName = seat[j].seat_name; 157 //去掉上中下 158 seatName = seatName.replace(/上|中|下/g, ''); 159 if (!my_seats[seatName]) { 160 my_seats[seatName] = parseInt(seat[j].seat_yupiao); 161 } else { 162 my_seats[seatName] = my_seats[seatName] + parseInt(seat[j].seat_yupiao); 163 } 164 } 165 //這裏myseat爲對象,須要轉換爲數組 166 //將定製坐席轉爲排序後的數組 167 data[i].my_seats = []; 168 for (iii = 0; iii < seatSort.length; iii++) { 169 if (typeof my_seats[seatSort[iii]] == 'number') data[i].my_seats.push({ 170 name: seatSort[iii], 171 yupiao: my_seats[seatSort[iii]] 172 }); 173 } 174 175 my_seats = {}; 176 } 177 178 return data; 179 }, 180 181 _timeSort: function (data, sort) { 182 data = _.sortBy(data, function (item) { 183 item = item.from_time.split(':'); 184 item = item[0] + '.' + item[1]; 185 item = parseFloat(item); 186 return item; 187 }); 188 if (sort == 'down') data.reverse(); 189 return data; 190 }, 191 192 _sumTimeSort: function (data, sort) { 193 data = _.sortBy(data, function (item) { 194 return parseInt(item.use_time); 195 }); 196 if (sort == 'down') data.reverse(); 197 return data; 198 }, 199 200 _priceSort: function (data, sort) { 201 data = _.sortBy(data, function (item) { 202 return item.min_price; 203 }); 204 if (sort == 'down') data.reverse(); 205 return data; 206 }, 207 208 //獲取導航欄排序後的數據 209 getSortData: function (data) { 210 var tmp = []; 211 var sort = this.props.state; 212 213 for (var k in sort) { 214 if (sort[k].length > 0) { 215 tmp = this['_' + k + 'Sort'](data, sort[k]) 216 return tmp; 217 } 218 } 219 }, 220 221 render: function () { 222 223 var main; 224 var data = this.formatData(this.props.data); 225 data = this.getSortData(data); 226 227 main = data.map(function(item) { 228 return <Item item={item}/>; 229 }); 230 231 return ( 232 <ul className="bus-list js_bus_list "> 233 {main} 234 </ul> 235 ); 236 } 237 238 }); 239 240 var App = React.createClass({ 241 getInitialState: function() { 242 return { 243 time: 'up', 244 sumTime: '', 245 price: '' 246 }; 247 }, 248 249 resetData: function () { 250 this.setState({ 251 time: '', 252 sumTime: '', 253 price: '' 254 }); 255 }, 256 257 setTime: function () { 258 this._setData('time'); 259 }, 260 261 setSumTime: function () { 262 this._setData('sumTime'); 263 }, 264 265 setPrice: function () { 266 this._setData('price'); 267 }, 268 269 _setData: function (key) { 270 var param = {}; 271 272 //若是設置當前key存在,則反置,不然清空篩選,設置默認值 273 if (this.state[key] != '') { 274 if (this.state[key] == 'up') param[key] = 'down'; 275 else param[key] = 'up'; 276 } else { 277 this.resetData(); 278 param[key] = 'down'; 279 } 280 this.setState(param); 281 }, 282 283 formatData: function (data) { 284 285 var item, seat; 286 var typeMap = { 287 'g': 'g', 288 'd': 'd', 289 't': 't', 290 'c': 'g' 291 }; 292 293 //出發時間對應的分鐘數 294 var fromMinute = 0; 295 296 //獲取當前班車日期當前的時間戳,這個數據是動態的,這裏寫死了 297 var d = 1464192000000; 298 var date = new Date(); 299 var now = parseInt(date.getTime() / 1000); 300 date.setTime(d); 301 var year = date.getFullYear(); 302 var month = date.getMonth(); 303 var day = date.getDate(); 304 var toBegin; 305 var seatName, seatIndex, iii; 306 307 //處理坐席問題,僅顯示二等座,一等座,特等座 無座 308 // 二等座 一等座 商務座 無座 動臥 特等座 309 var my_seats = {}; 310 var seatSort = ['二等座', '一等座', '硬座', '硬臥', '軟臥', '商務座', '無座', '動臥', '特等座', '軟座']; 311 312 for (var i = 0, len = data.length; i < len; i++) { 313 fromMinute = data[i].from_time.split(':'); 314 fromMinute[0] = fromMinute[0] + ''; 315 fromMinute[1] = fromMinute[1] + ''; 316 if ((fromMinute[0].charAt(0) == '0')) fromMinute[0] = fromMinute[0].charAt(1); 317 if ((fromMinute[1].charAt(0) == '0')) fromMinute[1] = fromMinute[1].charAt(1); 318 date = new Date(year, month, day, fromMinute[0], fromMinute[1], 0); 319 fromMinute = parseInt(date.getTime() / 1000) 320 toBegin = parseInt((fromMinute - now) / 60); 321 322 data[i].toBegin = toBegin; 323 324 //處理車次類型問題 325 data[i].my_train_number = typeMap[data[i].train_number.charAt(0).toLowerCase()] || 'other'; 326 327 seat = data[i].seats; 328 //全部餘票 329 data[i].sum_ticket = 0; 330 //最低價 331 data[i].min_price = null; 332 333 for (var j = 0, len1 = seat.length; j < len1; j++) { 334 if (!data[i].min_price || data[i].min_price > seat[j].seat_price) data[i].min_price = parseFloat(seat[j].seat_price); 335 data[i].sum_ticket += parseInt(seat[j].seat_yupiao); 336 337 //坐席問題若是坐席不包括上中下則去掉 338 seatName = seat[j].seat_name; 339 //去掉上中下 340 seatName = seatName.replace(/上|中|下/g, ''); 341 if (!my_seats[seatName]) { 342 my_seats[seatName] = parseInt(seat[j].seat_yupiao); 343 } else { 344 my_seats[seatName] = my_seats[seatName] + parseInt(seat[j].seat_yupiao); 345 } 346 } 347 //這裏myseat爲對象,須要轉換爲數組 348 //將定製坐席轉爲排序後的數組 349 data[i].my_seats = []; 350 for (iii = 0; iii < seatSort.length; iii++) { 351 if (typeof my_seats[seatSort[iii]] == 'number') data[i].my_seats.push({ 352 name: seatSort[iii], 353 yupiao: my_seats[seatSort[iii]] 354 }); 355 } 356 357 my_seats = {}; 358 } 359 360 return data; 361 }, 362 363 render: function () { 364 365 var main; 366 var data = this.formatData(this.props.data); 367 main = data.map(function(item) { 368 return <Item item={item}/>; 369 }); 370 371 return ( 372 <div> 373 <div className="js_sort_wrapper sort-bar-wrapper"> 374 <MySortBar state={this.state} setTime={this.setTime} setSumTime={this.setSumTime} setPrice={this.setPrice}/> 375 </div> 376 <MyList data={data} state={this.state} /> 377 </div> 378 ); 379 } 380 381 }); 382 383 var data = getListData(); 384 385 React.render( 386 <App data={data}/>, 387 document.getElementById('main') 388 ); 389 390 </script> 391 392 393 </body> 394 </html>
react的中文文檔整理較差,不少資料找不到,jsx語法比較怪異,不是全部人能接受,我去找模板循環時候壓根就沒找到,因此jsx有個特色,他讓你不得不去拆分你的組件,在我寫React代碼中,感受React代碼控制力度要重一點,可是若是沒有良好的架構能力,我能夠絕不誇張的說,你依舊寫很差業務代碼。
至於React與Vue的優劣,這個方面見仁見智吧,好了今天的文章到此爲止。
後續咱們可能會深刻分析下Vue的實現,在React Native上作深刻,有興趣的同窗能夠持續關注。
文章有任何不足錯誤,請您提出,由於小釵也是第二次使用React寫demo,若是使用不當請多包涵