用React的方式思考
2019獨角獸企業重金招聘Python工程師標準>>>
構建大型、反應迅捷的web app,我首選react。我們在facebook和instagram中都用了它,擴展的不錯。
當你構建APP時,React重塑了你的思考方式。我會在這篇文章中展現,用react構建應用的思考過程。
首先,JSON API 返回的數據如此這般:
[ {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"}, {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"}, {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"}, {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"}, {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"}, {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"} ];第一步:把UI分解成組件樹
你要做的第一件事就是,在設計圖上,把組件和子組件用線框出來,還要給他們命名。
thinking-in-react-components.png
如何劃分組件呢?方法同劃分函數或者對象一樣。你要遵循單一功能原則,就是說,一個組件只做一件事。如果組件不斷膨脹,就要把它再拆解成更小的子組件。
如果你的數據模型構建的好,那么你的UI和組件結構就會相應的更清晰明了。這是因為UI和數據模型遵循同樣的信息架構,這意味著如果數據模型足夠清晰,那么把UI分解成組件就是小事一樁。只需要把它拆分成組件,每個組件代表數據模型的一部分。
在這個APP中,我們有5個組件:
1、FilterableProductTable:包含整個APP
2、SearchBar:接收用戶輸入
3、ProductTable:根據用戶輸入,顯示和篩選數據集合
4、ProductCategoryRow:顯示每一個分類的標題
5、ProductRow:每一個產品,顯示一行
第二步:靜態版本
var ProductCategoryRow=React.createClass({render:function(){return(<tr><th colSpan="2">{this.props.category}</th></tr>);} }); var ProductRow=React.createClass({render:function(){var name=this.props.product.stocked?this.props.product.name:<span style={{color:red}}>{this.props.product.name}</span>;return(<tr><td>{name}</td><td>{this.props.product.price}</td></tr>);} });var ProductTable=React.createClass({render:function(){var rows=[];var lastCategory=null;this.props.products.forEach(function(product){if(product.category!==lastCategory){rows.push(<ProductCategoryRow category={product.category} key={product.category} />);}rows.push(<ProductRow product={product} key={product.name} />);lastCategory=product.category;});return(<table><thead><tr><th>Name</th><th>Price</th></tr></thead><tbody>{rows}</tbody></table>)} }); var SearchBar=React.createClass({render:function(){return(<form><input type="text" placeholder="Search..." /><p><input type="checkbox" />Only show products in stock</p></form>)} });var FilterableProductTable=React.createClass({render:function(){return(<div><SearchBar /><ProductTable products={this.props.products} /></div>)} })var PRODUCTS=[ {category:'Sporting Goods',price:'$49.99',stocked:true,name:'Football'}, {category:'Sporting Goods',price:'$49.99',stocked:true,name:'Baseball'}, {category:'Sporting Goods',price:'$49.99',stocked:false,name:'Basketball'}, {category:'Electronics',price:'$49.99',stocked:true,name:'iPod Touch'}, {category:'Electronics',price:'$49.99',stocked:false,name:'iPhone 5'}, {category:'Electronics',price:'$49.99',stocked:true,name:'Nexus 7'} ]; ReactDOM.render(<FilterableProductTable products={PRODUCTS} />,document.getElementById('container') );構建一個可以渲染數據模型的靜態APP,你要構建很多組件。這些組件重用了其他組件,并且用props傳遞數據。可以利用props,從父組件向子組件傳遞數據。在構建靜態版時,一定不能用state。state只用于交互,就是說,表示不斷變化的數據。
你可以自底向上或者自頂向下構建APP,如果自底向上,你先構建ProductRow;如果自頂向下,你先構建FilterableProductTable。簡單情形下,一般自頂向下。大型項目中,一般自底向上,并在構建過程中,編寫測試用例。
完成這一步,你會有一些可重用的組件,這些組件渲染你的數據模型。這些組件只有render方法,因為你的APP是靜態的。組件樹頂端的FilterableProductTable將你的數據模型作為prop。如果你改變數據模型,重新調用ReacDOM.render()方法,UI會更新。React的單向數據流讓一切模塊化、迅捷。
第三步:找出UI state的最小完備集
為了給UI添加交互,你需要觸發數據模型的變化。React用state輕松實現了這一點。
首先,你要想好可變狀態的最小集合。關鍵是DRY,不要重復自己。找出狀態的最小集合,用這個集合計算其他需要的狀態。例如,你構建一個TODO List,只要保留TODO items 數組,不要為計數保留一個額外的狀態變量。當你需要計數時,只需計算TODO items 數組的長度。
考慮我們所有的數據,我們有:原始產品列表,用戶鍵入的搜索文字,checkbox 的值,篩選過的產品列表。
為了確定哪一個是state,只要問三個問題:
1、它是否通過props從父組件傳入?如果是,可能不是state
2、它不斷變化嗎?如果不是,那么可能不是state
3、它可以用其他state或者props計算出來嗎?如果是,那么不是state
原始產品列表,作為props傳入,不是state;
搜索文本和checkbox是state,因為他們不斷變化,并且不能通過其他東西計算出來;
篩選后的產品列表不是state,因為它可以通過原始產品列表和搜索文字或checkbox的取值計算出來。
最終,state是:
用戶輸入的搜索文本
checkbox的值
第四步:確定state的位置
var ProductCategoryRow=React.createClass({render:function(){return (<tr><th colSpan="2">{this.props.category}</th></tr>);} }) var ProductRow=React.createClass({render:function(){var name=this.props.product.stocked?this.props.product.name:<span style={{color:'red'}}>{this.props.product.name}</span>;return(<tr><td>{name}</td><td>{this.props.product.price}</td></tr>)} }) var ProductTable=React.createClass({render:function(){var rows=[];var lastCategory=null;this.props.products.forEach(function(product){if(product.name.indexOf(this.props.filterText)===-1||(!product.stocked&&this.props.inStockOnly)){return;}if(product.category!==lastCategory){rows.push(<ProductCategoryRow category={product.category} key={product.category} />);}rows.push(<ProductRow product={product} key={product.name} />);lastCategory=product.category;}.bind(this));return (<table><thead><tr><th>Name</th><th>Price</th></tr></thead><tbody>{rows}</tbody></table>)} });var SearchBar=React.createClass({render:function(){return(<form><input type="text" placeholder="Search..." value={this.props.filterText}><p><input type="checkbox" checked={this.props.inStockOnly} />Only show products in stock</p></form>)} }) var FilterableProductTable=React.createClass({getInitialState:function(){return{filterText:'',inStockOnly:false}},render:function(){return(<div><SearchBar filterText={this.state.filterText} inStockOnly={this.state.inStockOnly} /><ProductTable products={this.props.products} filterText={this.state.filterText} inStockOnly={this.state.inStockOnly} /> </div>)} }) var PRODUCTS = [ {category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'}, {category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'}, {category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'}, {category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'}, {category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'}, {category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'} ]; ReactDOM.render( <FilterableProductTable products={PRODUCTS} />, document.getElementById('container') );現在,我們已經確定了state的最小集。下面,我們要確定哪個組件改變或擁有這些state。
記住:react的核心是沿著組件樹流動的單向數據。弄清楚哪一個組件擁有哪一個state,是最有挑戰的部分。
對于每一個state:
確定每一個根據這個state渲染的組件;
找到總組件(組件樹中,所有需要這個state的組件,上面的那個組件);
要么是總組件,要么是組件樹中更高層的組件,擁有這個state;
如果你找不到可以合理擁有這個state的組件,創建一個新的組件,這個組件只是為了持有這個state,并把這個組件加到組件樹中,在總組件之上
根據這一策略:
ProductTable需要根據state篩選產品列表,SearchBar需要展示搜索文本和選中狀態;
總組件是FilterableProductTable;
篩選文本和選擇值放在FilterableProductTable組件中很合適
棒!我們已經決定把state放在FilterableProductTable中。首先,在FilterableProductTable中添加getInitialState()方法,返回反映初始狀態的對象{filterText:'',inStockOnly:false}。然后,把filterText和inStockOnly作為prop傳到ProductTable和SearchBar中。最后,用這些props在ProductTable中篩選rows,在SearchBar中設置表單域的值。
第五步:添加逆向數據流
var ProductCategoryRow=React.createClass({render:function(){return (<tr><th colSpan="2">{this.props.category}</th></tr>);} }) var ProductRow=React.createClass({render:function(){var name=this.props.product.stocked?this.props.product.name:<span style={{color:'red'}}>{this.props.product.name}</span>;return(<tr><td>{name}</td><td>{this.props.product.price}</td></tr>)} }) var ProductTable=React.createClass({render:function(){var rows=[];var lastCategory=null;this.props.products.forEach(function(product){if(product.name.indexOf(this.props.filterText)===-1||(!product.stocked&&this.props.inStockOnly)){return;}if(product.category!==lastCategory){rows.push(<ProductCategoryRow category={product.category} key={product.category} />);}rows.push(<ProductRow product={product} key={product.name} />);lastCategory=product.category;}.bind(this));return (<table><thead><tr><th>Name</th><th>Price</th></tr></thead><tbody>{rows}</tbody></table>)} }) var SearchBar=React.createClass({handleChange:function(){this.props.onUserInput(this.refs.filterTextInput.value,this.refs.inStockOnlyInput.checked);},render:function(){return(<form><input type="text" placeholder="Search..." value={this.props.filterText} ref="filterTextInput" onChange={this.handleChange} /><p><input type="checkbox" checked={this.props.inStockOnly} ref="inStockOnlyInput" onChange={this.handleChange}>Only show products in stock</p></form>)} }) var FilterableProductTable=React.createClass({getInitialState:function(){return{filterText:'',inStockOnly:false}},handleUserInput:function(filterText,inStockOnly){this.setState({filterText:filterText,inStockOnly:inStockOnly});},render:function(){return(<div><SearchBar filterText={this.state.filterText} inStockOnly={this.state.inStockOnly} onUserInput={this.handleUserInput} /><ProductTable products={this.props.products} filterText={this.state.filterText} inStockOnly={this.state.inStockOnly} /></div>)} }) var PRODUCTS = [ {category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'}, {category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'}, {category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'}, {category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'}, {category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'}, {category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'} ]; ReactDOM.render(<FilterableProductTable products={PRODUCTS} />,document.getElementById('container') );目前,我們已經構建了一個可以正確渲染的APP,在渲染過程中,props和state函數沿著組件樹自上而下流動。現在,我們要支持數據反向流動:組件樹底層的form組件更新組件樹頂層的FilterableProductTable組件state。
React 讓這一數據流顯式表現出來,這樣,在理解程序如何運轉時會更容易。但是,相對于傳統的雙向數據綁定,你需要書寫更多。React提供了一個叫ReactLink的插件,讓這種模式像雙向數據綁定一樣方便,但在本文中,我采用顯式的方式。
在當前版本中,如果你鍵入或者勾選,不會有任何反應。因為我們已經將input中的value prop和從FilterableProductTable中傳入的state設置為相等。
我們希望當用戶改變表單時,我們能更新state來反應用戶的輸入。由于組件只能更新自己的state,FilterableProductTable將會傳遞一個回調函數給SearchBar,每當state更新時,這個回調函數就會觸發。我們可以用inputs上的onChange事件,監聽這一變化。每當onChange事件被觸發,由FilterableProductTable傳入的回調函數會調用setState()方法,app就會更新。
?
文/孫和David(簡書作者)
原文鏈接:http://www.jianshu.com/p/62f4535132a3
著作權歸作者所有,轉載請聯系作者獲得授權,并標注“簡書作者”。
轉載于:https://my.oschina.net/m2gIGj8g1iU/blog/806759
總結
以上是生活随笔為你收集整理的用React的方式思考的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CentOS 6.7安装python3.
- 下一篇: css3多行超出隐藏并打点点