我以前喜歡玩一款遊戲:全民飛機大戰,並且有點癡迷其中,若是你想站在遊戲的第一階梯,便須要不斷的練技術練裝備,可是騰訊的遊戲通常而言是有點噁心的,他會不斷的出新飛機、新裝備、新寵物,因此,不少時候你一個飛機以及裝備還沒滿級,新的裝備就又出來了,而且必定是更強!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的時候,最火的好像是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從這裏便走出了不日常的路,而他這樣作的意義是什麼,咱們還不知道。
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爲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 })();
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 })();
根據咱們以前的知識,這裏是建立了一個自定義標籤,而標籤返回的內容是:
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 })();
咱們如今將關注點放在其全部標籤的調用方,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 })();
這裏一樣是建立了一個標籤,而後最後一段代碼有所不一樣:
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