【JavaScript】ReactJS基礎

初探React,將咱們的View標籤化

前言

我以前喜歡玩一款遊戲:全民飛機大戰,並且有點癡迷其中,若是你想站在遊戲的第一階梯,便須要不斷的練技術練裝備,可是騰訊的遊戲通常而言是有點噁心的,他會不斷的出新飛機、新裝備、新寵物,因此,不少時候你一個飛機以及裝備還沒滿級,新的裝備就又出來了,而且必定是更強!javascript

因而不少人便直接拋棄當前的飛機與裝備,追求更好的,這個時候若是是人民幣玩家或者骨灰級大神玩家的話,基本能夠很快站在世界的頂端,一者是裝備好,一者是技術好,可是我不肯意投入太多錢,也不肯意投入過多精力,因而在一套極品裝備滿級後會積累資源,由於一代之間變化不會太大,到第二代甚至第三代纔開始換飛機換裝備,也基本處於了第一階梯,一直到一次遊戲大更新,直接致使我當前的飛機與裝備徹底二逼了,我當時一時腦熱投入了全部資源去刷新的極品裝備,最後鬧的血本無歸,因而便刪除了該遊戲,一年時間付諸東流!!!css

再回過頭來看最近兩年前端的變化,單是一個前端工程化工具就變了幾回,並且新出來的老是叫嚷着要替換以前的,grunt->gulp->webpack->es6html

再看前端框架的一些產量:backbone、angularJS、react、canJS、vueJS......前端

真有點亂花漸欲迷人眼的意思,彷佛前端技術也開始想要坑前端玩家,由於人家會了新技能,你就落後了,因而不少技術沉澱已經足夠的大神便直接在團隊使用某一技術,帶領團隊組員深刻了解了該技術的好,並大勢宣傳新技術。vue

不少人在這種狀況下就中招了!他們可能會拋棄現有技術棧,直接跟風新的技術,在現有裝備都沒滿級的狀況下又去刷新裝備,若是哪天一次遊戲玩法大更新,大神依舊一套極品裝備在那風騷,而炮灰倉庫中積累着一籮筐低等級的極品裝備,卻沒有可用的,不可謂不悲哀!java

一門技術從入門到精通,是須要時間的,在有限的時間中要學習那麼多的新技術,還得落地到實際工做中,而每一次新技術的落地都是對曾經架構的否認與推翻,這個成本不可謂不高,對一些創業團隊甚至是致命的。工做中也沒那麼多時間讓你折騰新東西,因此必定是瞭解清楚了一門再去學習其它的,不要半途而廢也不要盲目選型。node

我最近回顧了這幾年所學,能夠說技術棧沒有怎麼更新,可是我對我所習的每個技術基本上進入了深刻的瞭解:react

① 在MVVM還不太火的時候使用了MVC框架一直到最近,對爲何要使用這種模式,這種模式的好處有了比較深刻的瞭解,而且已經碰到了更復雜的業務邏輯webpack

② 當一個頁面過於複雜時(好比1000多行代碼的訂單填寫頁),我能經過幾年沉澱,將之拆分爲多個業務組件模塊,保持主控制器的業務清晰,代碼量維護在500行以內,而且各子模塊業務也清晰,根據model進行通訊git

③ 使用Grunt完成前端工程化,從構建項目,到打包壓縮項目,到優化項目,總的來講無往不利

④ ......

就編程方法,思惟習慣,解決問題的方法來講,與兩年前有了很大的變化,並且感受很難提升了。因而我認識到,就現有的裝備下,可能已經玩到極限了,可能到了跟風的時候了,而時下熱門的ReactJS彷佛是一個很好的切入點,React一端代碼多端運行的噱頭也足夠。

初識ReactJS

我最初接觸ReactJS的時候,最火的好像是angular,React Native也沒有出現,看了他的demo,對其局部刷新的實現很感興趣。結果,翻看源碼一看洋洋灑灑一萬多行代碼,因而立刻便退卻了。卻不想如今火成了這般模樣,身邊學習的人多,用於生產的少,我想幕後必然有黑手在推進!也能夠預測的是,1,2年後會有更好的框架會取代他,多是原團隊的自我推翻,也有多是Google公司又新出了什麼框架,畢竟前端最近幾年纔開始真正富客戶端,還有很長的路要走。固然,這不是咱們關心的重點,咱們這裏的重點是Hello world。

ReactJS的Hello World是這樣寫的:

複製代碼
 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <script src="build/react.js" type="text/javascript"></script>
 5     <script src="build/JSXTransformer.js" type="text/javascript"></script>
 6 </head>
 7 <body>
 8     <div id="example">
 9     </div>
10     <script type="text/jsx">
11       React.render(
12         <h1>Hello, world!</h1>,
13         document.getElementById('example')
14       );
15     </script>
16 </body>
17 </html>
複製代碼
<div id="example"><h1 data-reactid=".0">Hello, world!</h1></div>

React一來就搞了一個標新立異的地方:jsx(js擴展),說實話,這種作法真的有點大手筆,最初的這種聲明式標籤寫法,在我腦中基本能夠追溯到5年前的.net控件了,好比gridview與datalist組件。

在text/jsx中的代碼最初不會被瀏覽器理會,他會被react的JSXTransformer編譯爲常規的JS,而後瀏覽器才能解析。這裏與html模板會轉換爲js函數是一個道理,咱們有一種優化方案是模板預編譯,即:

在打包時候便將模板轉換爲js函數,免去在線解析的過程,react固然也能夠這樣作,這裏若是要解析的話,會是這個樣子:

1 React.render(
2   React.createElement("h1", null, "Hello, world!"),
3   document.getElementById('example')
4 );

由於render中的代碼能夠很複雜,render中的代碼寫法就是一種語法糖,幫助咱們更好的寫表現層代碼:render方法中能夠寫html與js混雜的代碼:

1 var data = [1,2,3];
2 React.render(
3     <h1>Hello, {data.toString(',')}!</h1>,
4     document.getElementById('example')
5 );
複製代碼
1 var data = [1,2,3];
2 React.render(
3     <h1>{
4         data.map(function(v, i) {
5             return <div>{i}-{v}</div>
6         })
7     }</h1>,
8     document.getElementById('example')
9 );
複製代碼

因此,react提供了不少類JS的語法,JSXTransformer至關於一個語言解釋器,而解析邏輯長達10000多行代碼,這個可不是通常屌絲能夠碰的,react從這裏便走出了不日常的路,而他這樣作的意義是什麼,咱們還不知道。

標籤化View

react提供了一個方法,將代碼組裝成一個組件,而後像HTML標籤同樣插入網頁:

複製代碼
 1 var Pili = React.createClass({
 2     render: function() {
 3         return <h1>Hello World!</h1>;
 4     }
 5 });
 6 
 7 React.render(
 8     <Pili />,
 9     document.getElementById('example')
10 );
複製代碼

所謂,聲明試編程,即是將須要的屬性寫到標籤上,以一個文本框爲例:

<input type="text" data-type="num" data-max="100" data-min="0" data-remove=true />

咱們想要輸入的是數字,有數字限制,並且在移動端輸入的時候,右邊會有一個X按鈕清除文本,這個即是咱們指望的聲明式標籤。

react中,標籤須要和原始的類發生通訊,好比屬性的讀取是這樣的:

複製代碼
 1 var Pili = React.createClass({
 2     render: function() {
 3         return <h1>Hello {this.props.name}!</h1>;
 4     }
 5 });
 6 
 7 React.render(
 8     <Pili name='霹靂布袋戲'/>,
 9     document.getElementById('example')
10 );
11 
12 //Hello 霹靂布袋戲!
複製代碼

上文中Pili即是一個組件,標籤使用法即是一個實例,聲明式寫法最終也會被編譯成通常的js方法,這個不是咱們如今關注的重點。

因爲class與for爲關鍵字,須要使用className與htmlFor替換

經過this.props對象能夠獲取組件的屬性,其中一個例外爲children,他表示組件的全部節點:

複製代碼
 1 var Pili = React.createClass({
 2     render: function() {
 3         return (
 4             <div>
 5                 {
 6                     this.props.children.map(function (child) {
 7                       return <div>{child}</div>
 8                     })
 9                 }
10             </div>
11         );
12     }
13 });
14 
15 React.render(
16     <Pili name='霹靂布袋戲'>
17         <span>素還真</span>
18         <span>葉小釵</span>
19     </Pili>
20     ,
21     document.getElementById('example')
22 );
複製代碼
複製代碼
1 <div id="Div1">
2     <div data-reactid=".0">
3         <div data-reactid=".0.0">
4             <span data-reactid=".0.0.0">素還真</span></div>
5         <div data-reactid=".0.1">
6             <span data-reactid=".0.1.0">葉小釵</span></div>
7     </div>
8 </div>
複製代碼

PS:return的語法與js語法不太同樣,不能隨便加分號

若是想限制某一個屬性必須是某一類型的話,便須要設置PropTypes屬性:

複製代碼
1 var Pili = React.createClass({
2     propType: {
3         //name必須有,而且必須是字符串
4         name:  React.PropTypes.string.isRequired
5     },
6     render: function() {
7         return <h1>Hello {this.props.name}!</h1>;
8     }
9 });
複製代碼

若是想設置屬性的默認值,則須要:

複製代碼
 1 var Pili = React.createClass({
 2     propType: {
 3         //name必須有,而且必須是字符串
 4         name:  React.PropTypes.string.isRequired
 5     },
 6     getDefaultProps : function () {
 7         return {
 8             title : '布袋戲'
 9         };
10     },
11     render: function() {
12         return <h1>Hello {this.props.name}!</h1>;
13     }
14 });
複製代碼

咱們仍然須要dom交互,咱們有時也須要獲取真實的dom節點,這個時候須要這麼作:

複製代碼
 1 var MyComponent = React.createClass({
 2   handleClick: function() {
 3     React.findDOMNode(this.refs.myTextInput).focus();
 4   },
 5   render: function() {
 6     return (
 7       <div>
 8         <input type="text" ref="myTextInput" />
 9         <input type="button" value="Focus the text input" onClick={this.handleClick} />
10       </div>
11     );
12   }
13 });
複製代碼

事件觸發的時候經過ref屬性獲取當前dom元素,而後可進行操做,咱們這裏看看返回的dom是什麼:

<input type="text" data-reactid=".0.0">

看來是真實的dom結構被返回了,另一個比較關鍵的事情,即是這裏的dom事件支持,須要細讀文檔:http://facebook.github.io/react/docs/events.html#supported-events

表單元素,屬於用戶與組件的交互,內容不能由props獲取,這個時候通常有狀態機獲取,所謂狀態機,即是會常常變化的屬性。

複製代碼
 1 var Input = React.createClass({
 2   getInitialState: function() {
 3     return {value: 'Hello!'};
 4   },
 5   handleChange: function(event) {
 6     this.setState({value: event.target.value});
 7   },
 8   render: function () {
 9     var value = this.state.value;
10     return (
11       <div>
12         <input type="text" value={value} onChange={this.handleChange} />
13         <p>{value}</p>
14       </div>
15     );
16   }
17 });
18 
19 React.render(<Input/>, document.body);
複製代碼

組件有其生命週期,每一個階段會觸發相關事件可被用戶捕捉使用:

Mounting:已插入真實 DOM
Updating:正在被從新渲染
Unmounting:已移出真實 DOM

通常來講,咱們會爲一個狀態發生先後綁定事件,react也是如此:

複製代碼
componentWillMount()
componentDidMount()
componentWillUpdate(object nextProps, object nextState)
componentDidUpdate(object prevProps, object prevState)
componentWillUnmount()
此外,React 還提供兩種特殊狀態的處理函數。
componentWillReceiveProps(object nextProps):已加載組件收到新的參數時調用
shouldComponentUpdate(object nextProps, object nextState):組件判斷是否從新渲染時調用
複製代碼

根據以前的經驗,會監控組件的生命週期的操做的時候,每每都是比較高階的應用了,咱們這裏暫時不予關注。

好了,以前的例子多半來源於阮一峯老師的教程,咱們這裏來一個簡單的驗收,便實現上述只能輸入數字的文本框:

複製代碼
 1 var NumText = React.createClass({
 2     getInitialState: function() {
 3         return {value: 50};
 4     },
 5     propTypes: {
 6         value: React.PropTypes.number
 7     },
 8     handleChange: function (e) {
 9         var v = parseInt(e.target.value);
10         if(v > this.props.max || v < this.props.min  ) return;
11         if(isNaN(v)) v = '';
12         this.setState({value: v});
13     },
14     render: function () {
15         return (
16             <input type="text" value={this.state.value} onChange={this.handleChange} />
17         );
18     }
19 });
20 
21 React.render(
22   <NumText min="0" max="100" />,
23   document.body
24 );
複製代碼

經過以上學習,咱們對React有了一個初步認識,如今咱們進入其todolist,看看其是如何實現的

此段參考:阮一峯老師的入門教程,http://www.ruanyifeng.com/blog/2015/03/react.html

TodoMVC

入口文件

TodoMVC爲MVC框架經典的demo,難度適中,而又能夠展現MVC的思想,咱們來看看React此處的入口代碼:

複製代碼
 1 <!doctype html>
 2 <html lang="en" data-framework="react">
 3 <head>
 4     <meta charset="utf-8">
 5     <title>React • TodoMVC</title>
 6     <link rel="stylesheet" href="node_modules/todomvc-common/base.css">
 7     <link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
 8 </head>
 9 <body>
10     <section class="todoapp">
11     </section>
12     <script src="node_modules/react/dist/react-with-addons.js"></script>
13     <script src="node_modules/react/dist/JSXTransformer.js"></script>
14     <script src="node_modules/director/build/director.js"></script>
15     <script src="js/utils.js"></script>
16     <script src="js/todoModel.js"></script>
17 
18     <script type="text/jsx" src="js/todoItem.jsx"></script>
19     <script type="text/jsx" src="js/footer.jsx"></script>
20     <script type="text/jsx" src="js/app.jsx"></script>
21 </body>
22 </html>
複製代碼

頁面很乾淨,除了react基本js與其模板解析文件外,還多了一個director.js,由於react自己不提供路由功能,因此路由的工做便須要插件,director即是路由插件,這個不是咱們今天學習的重點,而後是兩個js文件:

 1 var app = app || {};
 2 
 3 (function () {
 4   'use strict';
 5 
 6   app.Utils = {
 7     uuid: function () {
 8       /*jshint bitwise:false */
 9       var i, random;
10       var uuid = '';
11 
12       for (i = 0; i < 32; i++) {
13         random = Math.random() * 16 | 0;
14         if (i === 8 || i === 12 || i === 16 || i === 20) {
15           uuid += '-';
16         }
17         uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random))
18                     .toString(16);
19       }
20 
21       return uuid;
22     },
23 
24     pluralize: function (count, word) {
25       return count === 1 ? word : word + 's';
26     },
27 
28     store: function (namespace, data) {
29       if (data) {
30         return localStorage.setItem(namespace, JSON.stringify(data));
31       }
32 
33       var store = localStorage.getItem(namespace);
34       return (store && JSON.parse(store)) || [];
35     },
36 
37     extend: function () {
38       var newObj = {};
39       for (var i = 0; i < arguments.length; i++) {
40         var obj = arguments[i];
41         for (var key in obj) {
42           if (obj.hasOwnProperty(key)) {
43             newObj[key] = obj[key];
44           }
45         }
46       }
47       return newObj;
48     }
49   };
50 })();
utils
複製代碼
 1 var app = app || {};
 2 
 3 (function () {
 4   'use strict';
 5 
 6   var Utils = app.Utils;
 7   // Generic "model" object. You can use whatever
 8   // framework you want. For this application it
 9   // may not even be worth separating this logic
10   // out, but we do this to demonstrate one way to
11   // separate out parts of your application.
12   app.TodoModel = function (key) {
13     this.key = key;
14     this.todos = Utils.store(key);
15     this.onChanges = [];
16   };
17 
18   app.TodoModel.prototype.subscribe = function (onChange) {
19     this.onChanges.push(onChange);
20   };
21 
22   app.TodoModel.prototype.inform = function () {
23     Utils.store(this.key, this.todos);
24     this.onChanges.forEach(function (cb) { cb(); });
25   };
26 
27   app.TodoModel.prototype.addTodo = function (title) {
28     this.todos = this.todos.concat({
29       id: Utils.uuid(),
30       title: title,
31       completed: false
32     });
33 
34     this.inform();
35   };
36 
37   app.TodoModel.prototype.toggleAll = function (checked) {
38     // Note: it's usually better to use immutable data structures since they're
39     // easier to reason about and React works very well with them. That's why
40     // we use map() and filter() everywhere instead of mutating the array or
41     // todo items themselves.
42     this.todos = this.todos.map(function (todo) {
43       return Utils.extend({}, todo, { completed: checked });
44     });
45 
46     this.inform();
47   };
48 
49   app.TodoModel.prototype.toggle = function (todoToToggle) {
50     this.todos = this.todos.map(function (todo) {
51       return todo !== todoToToggle ?
52                 todo :
53                 Utils.extend({}, todo, { completed: !todo.completed });
54     });
55 
56     this.inform();
57   };
58 
59   app.TodoModel.prototype.destroy = function (todo) {
60     this.todos = this.todos.filter(function (candidate) {
61       return candidate !== todo;
62     });
63 
64     this.inform();
65   };
66 
67   app.TodoModel.prototype.save = function (todoToSave, text) {
68     this.todos = this.todos.map(function (todo) {
69       return todo !== todoToSave ? todo : Utils.extend({}, todo, { title: text });
70     });
71 
72     this.inform();
73   };
74 
75   app.TodoModel.prototype.clearCompleted = function () {
76     this.todos = this.todos.filter(function (todo) {
77       return !todo.completed;
78     });
79 
80     this.inform();
81   };
82 
83 })();
複製代碼

utils爲簡單的工具類,不予理睬;不管何時數據層必定是MVC的重點,這裏稍微給予一點關注:

① model層實現了一個簡單的事件訂閱通知系統

② 從類實現來講,他僅有三個屬性,key(存儲與localstorage的命名空間),todos(真實的數據對象),changes(事件集合)

③ 與backbone的model不一樣,backbone的數據操做佔了其實現大部分篇幅,backbone的TodoMVC會完整定義Model的增刪差改依次觸發的事件,因此Model定義結束,程序就有了完整的脈絡,而咱們看react這裏有點「弱化」數據處理的感受

④ 總的來講,整個Model的方法皆在操做todos數據,subscribe用於註冊事件,每次操做皆會通知changes函數響應,而且存儲到localstorage,從重構的角度來講inform其實只應該完成通知的工做,存儲的事情不該該作,可是這與咱們今天所學沒有什麼管理,不予理睬,接下來咱們進入View層的代碼。

組件化編程

React號稱組件化編程,咱們從標籤化、聲明式編程的角度來一塊兒看看他第一個View TodoItem的實現:

  1 var app = app || {};
  2 
  3 (function () {
  4     'use strict';
  5 
  6     var ESCAPE_KEY = 27;
  7     var ENTER_KEY = 13;
  8 
  9     app.TodoItem = React.createClass({
 10         handleSubmit: function (event) {
 11             var val = this.state.editText.trim();
 12             if (val) {
 13                 this.props.onSave(val);
 14                 this.setState({editText: val});
 15             } else {
 16                 this.props.onDestroy();
 17             }
 18         },
 19 
 20         handleEdit: function () {
 21             this.props.onEdit();
 22             this.setState({editText: this.props.todo.title});
 23         },
 24 
 25         handleKeyDown: function (event) {
 26             if (event.which === ESCAPE_KEY) {
 27                 this.setState({editText: this.props.todo.title});
 28                 this.props.onCancel(event);
 29             } else if (event.which === ENTER_KEY) {
 30                 this.handleSubmit(event);
 31             }
 32         },
 33 
 34         handleChange: function (event) {
 35             this.setState({editText: event.target.value});
 36         },
 37 
 38         getInitialState: function () {
 39             return {editText: this.props.todo.title};
 40         },
 41 
 42         /**
 43          * This is a completely optional performance enhancement that you can
 44          * implement on any React component. If you were to delete this method
 45          * the app would still work correctly (and still be very performant!), we
 46          * just use it as an example of how little code it takes to get an order
 47          * of magnitude performance improvement.
 48          */
 49         shouldComponentUpdate: function (nextProps, nextState) {
 50             return (
 51                 nextProps.todo !== this.props.todo ||
 52                 nextProps.editing !== this.props.editing ||
 53                 nextState.editText !== this.state.editText
 54             );
 55         },
 56 
 57         /**
 58          * Safely manipulate the DOM after updating the state when invoking
 59          * `this.props.onEdit()` in the `handleEdit` method above.
 60          * For more info refer to notes at https://facebook.github.io/react/docs/component-api.html#setstate
 61          * and https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate
 62          */
 63         componentDidUpdate: function (prevProps) {
 64             if (!prevProps.editing && this.props.editing) {
 65                 var node = React.findDOMNode(this.refs.editField);
 66                 node.focus();
 67                 node.setSelectionRange(node.value.length, node.value.length);
 68             }
 69         },
 70 
 71         render: function () {
 72             return (
 73                 <li className={React.addons.classSet({
 74                     completed: this.props.todo.completed,
 75                     editing: this.props.editing
 76                 })}>
 77                     <div className="view">
 78                         <input
 79                             className="toggle"
 80                             type="checkbox"
 81                             checked={this.props.todo.completed}
 82                             onChange={this.props.onToggle}
 83                         />
 84                         <label onDoubleClick={this.handleEdit}>
 85                             {this.props.todo.title}
 86                         </label>
 87                         <button className="destroy" onClick={this.props.onDestroy} />
 88                     </div>
 89                     <input
 90                         ref="editField"
 91                         className="edit"
 92                         value={this.state.editText}
 93                         onBlur={this.handleSubmit}
 94                         onChange={this.handleChange}
 95                         onKeyDown={this.handleKeyDown}
 96                     />
 97                 </li>
 98             );
 99         }
100     });
101 })();
TodoItem

根據咱們以前的知識,這裏是建立了一個自定義標籤,而標籤返回的內容是:

複製代碼
render: function () {
    return (
        <li className={React.addons.classSet({
            completed: this.props.todo.completed,
            editing: this.props.editing
        })}>
            <div className="view">
                <input
                    className="toggle"
                    type="checkbox"
                    checked={this.props.todo.completed}
                    onChange={this.props.onToggle}
                />
                <label onDoubleClick={this.handleEdit}>
                    {this.props.todo.title}
                </label>
                <button className="destroy" onClick={this.props.onDestroy} />
            </div>
            <input
                ref="editField"
                className="edit"
                value={this.state.editText}
                onBlur={this.handleSubmit}
                onChange={this.handleChange}
                onKeyDown={this.handleKeyDown}
            />
        </li>
    );
}
複製代碼

要展現這個View須要依賴其屬性與狀態:

getInitialState: function () {
    return {editText: this.props.todo.title};
},

這裏沒有屬性的描寫,而他自己也僅僅是標籤組件,更多的信息咱們須要去看調用方,該組件顯示的是body部分,TodoMVC還有footer部分的操做工具條,這裏的實現便比較簡單了:

 1 var app = app || {};
 2 
 3 (function () {
 4     'use strict';
 5 
 6     app.TodoFooter = React.createClass({
 7         render: function () {
 8             var activeTodoWord = app.Utils.pluralize(this.props.count, 'item');
 9             var clearButton = null;
10 
11             if (this.props.completedCount > 0) {
12                 clearButton = (
13                     <button
14                         className="clear-completed"
15                         onClick={this.props.onClearCompleted}>
16                         Clear completed
17                     </button>
18                 );
19             }
20 
21             // React idiom for shortcutting to `classSet` since it'll be used often
22             var cx = React.addons.classSet;
23             var nowShowing = this.props.nowShowing;
24             return (
25                 <footer className="footer">
26                     <span className="todo-count">
27                         <strong>{this.props.count}</strong> {activeTodoWord} left
28                     </span>
29                     <ul className="filters">
30                         <li>
31                             <a
32                                 href="#/"
33                                 className={cx({selected: nowShowing === app.ALL_TODOS})}>
34                                     All
35                             </a>
36                         </li>
37                         {' '}
38                         <li>
39                             <a
40                                 href="#/active"
41                                 className={cx({selected: nowShowing === app.ACTIVE_TODOS})}>
42                                     Active
43                             </a>
44                         </li>
45                         {' '}
46                         <li>
47                             <a
48                                 href="#/completed"
49                                 className={cx({selected: nowShowing === app.COMPLETED_TODOS})}>
50                                     Completed
51                             </a>
52                         </li>
53                     </ul>
54                     {clearButton}
55                 </footer>
56             );
57         }
58     });
59 })();
TodoFooter

咱們如今將關注點放在其全部標籤的調用方,app.jsx(TodoApp),由於我沒看見這個TodoMVC的控制器在哪,也就是我沒有看見控制邏輯的js文件在哪,因此控制流程的代碼只能在這裏了:

  1 var app = app || {};
  2 
  3 (function () {
  4     'use strict';
  5 
  6     app.ALL_TODOS = 'all';
  7     app.ACTIVE_TODOS = 'active';
  8     app.COMPLETED_TODOS = 'completed';
  9     var TodoFooter = app.TodoFooter;
 10     var TodoItem = app.TodoItem;
 11 
 12     var ENTER_KEY = 13;
 13 
 14     var TodoApp = React.createClass({
 15         getInitialState: function () {
 16             return {
 17                 nowShowing: app.ALL_TODOS,
 18                 editing: null
 19             };
 20         },
 21 
 22         componentDidMount: function () {
 23             var setState = this.setState;
 24             var router = Router({
 25                 '/': setState.bind(this, {nowShowing: app.ALL_TODOS}),
 26                 '/active': setState.bind(this, {nowShowing: app.ACTIVE_TODOS}),
 27                 '/completed': setState.bind(this, {nowShowing: app.COMPLETED_TODOS})
 28             });
 29             router.init('/');
 30         },
 31 
 32         handleNewTodoKeyDown: function (event) {
 33             if (event.keyCode !== ENTER_KEY) {
 34                 return;
 35             }
 36 
 37             event.preventDefault();
 38 
 39             var val = React.findDOMNode(this.refs.newField).value.trim();
 40 
 41             if (val) {
 42                 this.props.model.addTodo(val);
 43                 React.findDOMNode(this.refs.newField).value = '';
 44             }
 45         },
 46 
 47         toggleAll: function (event) {
 48             var checked = event.target.checked;
 49             this.props.model.toggleAll(checked);
 50         },
 51 
 52         toggle: function (todoToToggle) {
 53             this.props.model.toggle(todoToToggle);
 54         },
 55 
 56         destroy: function (todo) {
 57             this.props.model.destroy(todo);
 58         },
 59 
 60         edit: function (todo) {
 61             this.setState({editing: todo.id});
 62         },
 63 
 64         save: function (todoToSave, text) {
 65             this.props.model.save(todoToSave, text);
 66             this.setState({editing: null});
 67         },
 68 
 69         cancel: function () {
 70             this.setState({editing: null});
 71         },
 72 
 73         clearCompleted: function () {
 74             this.props.model.clearCompleted();
 75         },
 76 
 77         render: function () {
 78             var footer;
 79             var main;
 80             var todos = this.props.model.todos;
 81 
 82             var shownTodos = todos.filter(function (todo) {
 83                 switch (this.state.nowShowing) {
 84                 case app.ACTIVE_TODOS:
 85                     return !todo.completed;
 86                 case app.COMPLETED_TODOS:
 87                     return todo.completed;
 88                 default:
 89                     return true;
 90                 }
 91             }, this);
 92 
 93             var todoItems = shownTodos.map(function (todo) {
 94                 return (
 95                     <TodoItem
 96                         key={todo.id}
 97                         todo={todo}
 98                         onToggle={this.toggle.bind(this, todo)}
 99                         onDestroy={this.destroy.bind(this, todo)}
100                         onEdit={this.edit.bind(this, todo)}
101                         editing={this.state.editing === todo.id}
102                         onSave={this.save.bind(this, todo)}
103                         onCancel={this.cancel}
104                     />
105                 );
106             }, this);
107 
108             var activeTodoCount = todos.reduce(function (accum, todo) {
109                 return todo.completed ? accum : accum + 1;
110             }, 0);
111 
112             var completedCount = todos.length - activeTodoCount;
113 
114             if (activeTodoCount || completedCount) {
115                 footer =
116                     <TodoFooter
117                         count={activeTodoCount}
118                         completedCount={completedCount}
119                         nowShowing={this.state.nowShowing}
120                         onClearCompleted={this.clearCompleted}
121                     />;
122             }
123 
124             if (todos.length) {
125                 main = (
126                     <section className="main">
127                         <input
128                             className="toggle-all"
129                             type="checkbox"
130                             onChange={this.toggleAll}
131                             checked={activeTodoCount === 0}
132                         />
133                         <ul className="todo-list">
134                             {todoItems}
135                         </ul>
136                     </section>
137                 );
138             }
139 
140             return (
141                 <div>
142                     <header className="header">
143                         <h1>todos</h1>
144                         <input
145                             ref="newField"
146                             className="new-todo"
147                             placeholder="What needs to be done?"
148                             onKeyDown={this.handleNewTodoKeyDown}
149                             autoFocus={true}
150                         />
151                     </header>
152                     {main}
153                     {footer}
154                 </div>
155             );
156         }
157     });
158 
159     var model = new app.TodoModel('react-todos');
160 
161     function render() {
162         React.render(
163             <TodoApp model={model}/>,
164             document.getElementsByClassName('todoapp')[0]
165         );
166     }
167 
168     model.subscribe(render);
169     render();
170 })();
TodoAPP

這裏一樣是建立了一個標籤,而後最後一段代碼有所不一樣:

複製代碼
 1 var model = new app.TodoModel('react-todos');
 2 
 3 function render() {
 4     React.render(
 5         <TodoApp model={model}/>,
 6         document.getElementsByClassName('todoapp')[0]
 7     );
 8 }
 9 
10 model.subscribe(render);
11 render();
複製代碼

① 這裏建立了一個Model的實例,咱們知道建立的時候,todos便由localstorage獲取了數據(若是有的話)

② 這裏了定義了一個方法,以todoapp爲容器,裝載標籤

③ 爲model訂閱render方法,意思是每次model有變化都將從新渲染頁面,這裏的代碼比較關鍵,按照代碼所示,每次數據變化都應該執行render方法,若是list數量比較多的話,每次接從新渲染豈不是浪費性能,但真實使用過程當中,能夠看到React居然是局部刷新的,他這個機制很是牛逼啊!

④ 最後執行了render方法,開始了TodoApp標籤的渲染,咱們這裏再將TodoApp的渲染邏輯貼出來

複製代碼
 1 render: function () {
 2         var footer;
 3         var main;
 4         var todos = this.props.model.todos;
 5 
 6         var shownTodos = todos.filter(function (todo) {
 7             switch (this.state.nowShowing) {
 8             case app.ACTIVE_TODOS:
 9                 return !todo.completed;
10             case app.COMPLETED_TODOS:
11                 return todo.completed;
12             default:
13                 return true;
14             }
15         }, this);
16 
17         var todoItems = shownTodos.map(function (todo) {
18             return (
19                 <TodoItem
20                     key={todo.id}
21                     todo={todo}
22                     onToggle={this.toggle.bind(this, todo)}
23                     onDestroy={this.destroy.bind(this, todo)}
24                     onEdit={this.edit.bind(this, todo)}
25                     editing={this.state.editing === todo.id}
26                     onSave={this.save.bind(this, todo)}
27                     onCancel={this.cancel}
28                 />
29             );
30         }, this);
31 
32         var activeTodoCount = todos.reduce(function (accum, todo) {
33             return todo.completed ? accum : accum + 1;
34         }, 0);
35 
36         var completedCount = todos.length - activeTodoCount;
37 
38         if (activeTodoCount || completedCount) {
39             footer =
40                 <TodoFooter
41                     count={activeTodoCount}
42                     completedCount={completedCount}
43                     nowShowing={this.state.nowShowing}
44                     onClearCompleted={this.clearCompleted}
45                 />;
46         }
47 
48         if (todos.length) {
49             main = (
50                 <section className="main">
51                     <input
52                         className="toggle-all"
53                         type="checkbox"
54                         onChange={this.toggleAll}
55                         checked={activeTodoCount === 0}
56                     />
57                     <ul className="todo-list">
58                         {todoItems}
59                     </ul>
60                 </section>
61             );
62         }
63 
64         return (
65             <div>
66                 <header className="header">
67                     <h1>todos</h1>
68                     <input
69                         ref="newField"
70                         className="new-todo"
71                         placeholder="What needs to be done?"
72                         onKeyDown={this.handleNewTodoKeyDown}
73                         autoFocus={true}
74                     />
75                 </header>
76                 {main}
77                 {footer}
78             </div>
79         );
80     }
複製代碼

說句實話,這段代碼不知爲何有一些使人感到難受......

① 他首先獲取了注入的model實例,獲取其所需的數據todos,注入點在:

<TodoApp model={model}/>

② 而後他由自身狀態機,獲取真實要顯示的項目,其實這裏若是不考慮路由的變化,徹底顯示便可

複製代碼
1 getInitialState: function () {
2     return {
3         nowShowing: app.ALL_TODOS,
4         editing: null
5     };
6 },
複製代碼

③ 數據獲取成功後,便使用該數據組裝爲一個個獨立的TodoItem標籤:

複製代碼
 1 var todoItems = shownTodos.map(function (todo) {
 2     return (
 3         <TodoItem
 4             key={todo.id}
 5             todo={todo}
 6             onToggle={this.toggle.bind(this, todo)}
 7             onDestroy={this.destroy.bind(this, todo)}
 8             onEdit={this.edit.bind(this, todo)}
 9             editing={this.state.editing === todo.id}
10             onSave={this.save.bind(this, todo)}
11             onCancel={this.cancel}
12         />
13     );
14 }, this);
複製代碼

標籤具備不少事件,這裏要注意一下各個事件這裏事件綁定與控制器上綁定有何不一樣

④ 而後其作了一些工做處理底部工具條或者頭部所有選中的工做

⑤ 最後開始渲染整個標籤:

複製代碼
 1 return (
 2     <div>
 3         <header className="header">
 4             <h1>todos</h1>
 5             <input
 6                 ref="newField"
 7                 className="new-todo"
 8                 placeholder="What needs to be done?"
 9                 onKeyDown={this.handleNewTodoKeyDown}
10                 autoFocus={true}
11             />
12         </header>
13         {main}
14         {footer}
15     </div>
16 );
複製代碼

該標籤事實上爲3個模塊組成的了:header部分、body部分、footer部分,模塊與模塊之間的通訊依賴即是model數據了,由於這裏最終的渲染皆在app的render處,而render處渲染全部標籤所有共同依賴於一個model,就算這裏依賴於多個model,只要是統一在render處作展現便可。

流程分析

咱們前面理清了整個脈絡,接下來咱們理一理幾個關鍵脈絡:

① 新增

TodoApp爲其頭部input標籤綁定了一個onKeyDown事件,事件代理到了handleNewTodoKeyDown:

複製代碼
 1 handleNewTodoKeyDown: function (event) {
 2     if (event.keyCode !== ENTER_KEY) {
 3         return;
 4     }
 5 
 6     event.preventDefault();
 7 
 8     var val = React.findDOMNode(this.refs.newField).value.trim();
 9 
10     if (val) {
11         this.props.model.addTodo(val);
12         React.findDOMNode(this.refs.newField).value = '';
13     }
14 },
複製代碼

由於用戶輸入的數據不能由屬性或者狀態值獲取,這裏使用了dom操做的方法獲取輸入數據,這裏的鉤子是ref,事件觸發了model新增一條記錄,而且將文本框置爲空,如今咱們進入model新增的邏輯:

複製代碼
1 app.TodoModel.prototype.addTodo = function (title) {
2   this.todos = this.todos.concat({
3     id: Utils.uuid(),
4     title: title,
5     completed: false
6   });
7 
8   this.inform();
9 };
複製代碼

model以最簡的方式構造了一個數據對象,改變了todos的值,而後通知model發生了變化,而咱們都知道informa程序幹了兩件事:

1 app.TodoModel.prototype.inform = function () {
2   Utils.store(this.key, this.todos);
3   this.onChanges.forEach(function (cb) { cb(); });
4 };

存儲localstorage、觸發訂閱model變化的回調,也就是:

複製代碼
1 function render() {
2     React.render(
3         <TodoApp model={model}/>,
4         document.getElementsByClassName('todoapp')[0]
5     );
6 }
7 
8 model.subscribe(render);
複製代碼

因而整個標籤可恥的從新渲染了,咱們再來看看編輯是怎麼回事:

② 編輯

這個編輯便與TodoApp沒有什麼關係了:

1 <label onDoubleClick={this.handleEdit}>
2     {this.props.todo.title}
3 </label>

當雙擊標籤項時,觸發了代理的處理程序:

1 handleEdit: function () {
2     this.props.onEdit();
3     this.setState({editText: this.props.todo.title});
4 },

這裏他作了兩個事情:

onEdit,爲父標籤注入的方法,他這裏執行函數做用域是指向this.props的,因此外層定義時指定了做用域:

複製代碼
 1 return (
 2     <TodoItem
 3         key={todo.id}
 4         todo={todo}
 5         onToggle={this.toggle.bind(this, todo)}
 6         onDestroy={this.destroy.bind(this, todo)}
 7         onEdit={this.edit.bind(this, todo)}
 8         editing={this.state.editing === todo.id}
 9         onSave={this.save.bind(this, todo)}
10         onCancel={this.cancel}
11     />
12 );
複製代碼

其次,他改變了自身狀態機,而狀態機或者屬性的變化皆會引發標籤從新渲染,而後當觸發keydown事件後,完成的邏輯便與上面一致了

思考

通過以前的學習,咱們對React有了一個大概的瞭解,是時候搬出React設計的初衷了:

 Just the ui
 virtual dom
 data flow

後面兩個概念還沒強烈的感觸,這裏僅僅對Just the ui有一些認識,彷佛React僅僅提供了MVC中View的實現,可是這個View又強大到能夠拋棄C了,能夠看到上述代碼控制器被無限的弱化了,而我以爲React其實真實想提供的多是一種開發方式的思路,React即是如何幫你實現這種思路的方案:

模塊化編程、組件化編程、標籤化編程,多是React真正想表達的思想

咱們在組織負責業務邏輯時,也會分模塊、分UI,可是咱們通常是採用控制器調用組件的方式使用,React這裏不一樣的一點是使用標籤分模塊,孰優孰劣要真實開發過生產項目的朋友才能認識,真實的應用路由的功能必不可少,應該有很多插件會主動抱大腿,但使用靈活性仍然得項目實踐驗證。

react自己很乾淨,不包括模塊加載的機制,真正發佈生產前須要經過webpack打包處理,可是對於複雜項目來講,按需加載是必不可少的,這塊不知道如何

而個人關注點仍然落在了樣式上,以前作組件或者作頁面時,有一個優化方案,是將對應的樣式做爲一個View的依賴項加載,一個View保持最小的html&css&js量加載,而react對樣式與動畫一塊的支持如何,也須要生產驗證;複雜的項目開發,Model的設計必定是相當重要的,也許借鑑Backbone Model的實現+React的View處理,會是一個不錯的選擇

最後,由於如今沒有生產項目能讓我使用React試水,過多的話基本就是意淫了,根據我以前MVC的使用經驗,感受靈活性上估計React仍然有一段路要走,可是其模塊化編程的思路卻是對個人項目有莫大的指導做用,對於這門技術的深刻,通過今天的學習,我打算再觀望一下,不知道angularJS怎麼樣,我也許該對這門MVVM的框架展開調研

 

參考資料:

http://www.cnblogs.com/yexiaochai/p/4853398.html

http://stackoverflow.com/questions/17585787/whats-data-reactid-attribute-in-html

相關文章
相關標籤/搜索