從DOM操做看Vue&React的前端組件化,順帶補齊React的demo

前言

接上文:談談我對前端組件化中「組件」的理解,順帶寫個Vue與React的demojavascript

上次寫完博客後,有朋友反應第一內容有點深,看着迷迷糊糊;第二是感受沒什麼使用場景,太過業務化,還不如直接寫Vue&react的源碼分析,我感受這裏有必要說下個人認識。css

首先,要寫源碼分析很難,第一是他原本就很難,因此通常咱們是想了解他實現的思路而不是代碼;html

第二每一個開發者有本身發風格,因此你要完全讀懂一我的的代碼不容易,除非你是帶着當時做者一樣的問題不斷的尋找解決方案,不斷的重構,纔可能理解用戶的意圖。前端

咱們上一次作的事情其實就是根據本身實際的工做經驗作了和外面框架相似的事情,雖然代碼的健壯、優雅程度跟不上,但卻和其它做者同樣爲解決一樣的問題思考得出的方案,上次作的太晚了,後面就草草結束,事實上在我Demo過程當中發現一個事實:業務代碼都是差很少的,只是一些細節點不同,因此決定產品質量的依舊是開發者的業務代碼能力,框架只是助力而已。java

不能瞭解做者的意圖,不能提升自己的編程水平,就算用上了React&Vue這類組件化的框架,也組織很差代碼;事實上這類代碼由於是面向大型應用的,反而更考驗一我的的架構能力,因此你們要多注重內在修養的提高哦。react

下面咱們進入今天的正題,這裏依舊提供一些幫助理解的資料:git

github

代碼地址:https://github.com/yexiaochai/module/github

演示地址:http://yexiaochai.github.io/module/me/index.htmlweb

若是對文中的一些代碼比較疑惑,能夠對比着看看這些文章:面試

【一次面試】再談javascript中的繼承

【移動前端開發實踐】從無到有(統計、請求、MVC、模塊化)H5開發須知

【組件化開發】前端進階篇之如何編寫可維護可升級的代碼

預覽

使用Vue的思考

由於第一個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操做呢?咱們下面就來試試。

如何擺脫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 });
View Code

就這種簡單的改變,貌似便擺脫了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的時候,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 Code

他這個語法聽說是讓開發變得更簡單了,我反正是不喜歡,這裏有個很差的地方,以前數據實體所有是在根View上實例化的,而後注入給子View,React這裏屬性徹底獨享了,如今我觸發了狀態的改變,如何通知到list組件從新渲染排序呢?

React 組件通訊

這裏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>
View Code

總結

react的中文文檔整理較差,不少資料找不到,jsx語法比較怪異,不是全部人能接受,我去找模板循環時候壓根就沒找到,因此jsx有個特色,他讓你不得不去拆分你的組件,在我寫React代碼中,感受React代碼控制力度要重一點,可是若是沒有良好的架構能力,我能夠絕不誇張的說,你依舊寫很差業務代碼。

至於React與Vue的優劣,這個方面見仁見智吧,好了今天的文章到此爲止。

後續咱們可能會深刻分析下Vue的實現,在React Native上作深刻,有興趣的同窗能夠持續關注。

文章有任何不足錯誤,請您提出,由於小釵也是第二次使用React寫demo,若是使用不當請多包涵

相關文章
相關標籤/搜索