標籤:
以下是React官方文檔中的Thinking inReact文章的翻譯,第一次翻譯英文的文章,肯定有很多不正確的地方,還望多多包涵。
原文地址:https://facebook.github.io/react/docs/thinking-in-react.html
原文開始
---------------------------------------------我是分隔字元------------------------------------------
Thinking in React
by Pete Hunt
React是什嗎?我的看法是,它是用javascript建立大型,快速web應用的首要方式。Fackbook和Instagram為我們做了很好的測試。
React的其中一個非常厲害的部分就是它是讓你在構建你的應用時如何去思考的。接下來,我會帶你體驗一下怎樣使用React去構建一個有搜尋功能的產品資料表。
Start with a mock 想象一下我們已經有了JSON資料的API和設計師給的網站模型。我們的設計師明顯不怎麼樣因為他給的模型是這樣的:
我們的JSON介面返回的資料是這樣的:
Step 1: Break the UI into a component hierarchy(把UI分割成組件層)
你要做的第一件事就是在組件周圍畫上盒子並且給它們取一個名字。有過你和網頁設計師一起工作,那他們可能已經做過這個工作了,去和他們談談。他們的
photoshop layer的名字,最後應該就會成為你的React組件的名字。但是你怎麼知道誰應該成為一個單獨組件呢?用這個方法就可以了:根據你是否需要去建立一個新的方法或者對象就可以決定是否是一個組件。這就是單一職責原則,原則就是:一個組件在理想狀態下只做一件事情。如果之後需要擴充,那麼就把它分割成子組件。
如果你經常向使用者展示JSON資料模型,你就會發現如果你的模型建立正確的話,你的UI(也就是你的組件結構)的布局就不會有問題。因為UI和資料模型都是依靠相同的資訊結構,這也就以為這把你的UI分隔成組件是很平常的。去把UI分隔成代表單塊資料模型的組件吧。
你可以看到在我們簡單地應用中有五個組件,
1.FilterableProductTable(橙色): 包含了應用這個整體。
2.SearchBar(藍色):接收了使用者輸入
3.ProductTable(綠色):依據使用者輸入展示和過濾資料
4.ProductCategoryRow(天藍色)地區標題
5.ProductRow(紅色):展示每個產品的行
看一下ProductTable,你就會發現表頭(包含‘Names’和‘Price’標籤)不是它自己的組件。這是一種偏好,對此有很多的爭論。在這個例子中,我把它放在了ProductTable裡邊,是因為它是渲染ProductTable資料集中的一部分。然而,如果這個表頭變複雜了(例如:我們加入了排序功能),它就得建立自己的ProductTableHeader元素。現在我們已經確認了我們用例中的組件,那就把它們層次化。組件中的小工具在層次化中應該表現成子類。
Step 2: Build a static version in React(在React中建立一個靜態版本)
現在我們的組件已經層次化了,是時間去實現我們的應用了。最簡單的方式是建立一個UI是依靠你的資料模型渲染的但是並沒有互動的版本。最好要拆解這些步驟,因為建立一個靜態版本需要在沒有想法的情況下寫很多的代碼,添加互動就需要思考很多並且寫很少的代碼。下面會知道為什麼。
建立一個渲染你的資料模型的應用的靜態版本,你會想去建立能複用其他組建的組件,並且使用props傳遞資料。props是父輩向下傳遞資料的方式。如果你很熟悉state的概念,不要在這個靜態版本裡面用state。state僅僅是為了互動而存在,裡邊的資料是一直在變的。盡然這是一個靜態版本,那就不要用state。
你可以從上往下或反向建立,也就是你可以依據應用的層級從高層向底層(例如從FilterableProductTable開始)或反向實現(從ProductRow開始)。再簡單的例子中,從上至下更容易,在大型項目中,從下向上地建立和測試應用更簡單。
在這一步的最後,你會用一個可複用的組件庫去渲染你的資料模型。既然這是一個應用的靜態版本,那麼組件就只會擁有render()方法。在組件層頂端的組件就會把資料模型作為prop。如果你修改底層的資料模型,然後再次調用ReactDOM.render(),UI會被更新。很容易就可以看到你的UI怎樣更新,哪裡做了改變,之後就沒有什麼複雜的東西了。React是依靠單向資料流使得所有事情模組化和快速。
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: '$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'));
Step 3: Identify the minimal (but complete) representation of UI state(找到最小的UI state的代表)
為了實現你的UI的互動性,你需要去觸發你的底層資料模型的改變。React通過運用state使這變得很容易。
為了正確的建立你的應用,第一步應該考慮的是你的應用需要的最小單位的可變state。關鍵技巧就是:不要重複。找出你的應用需要的state絕對最小代表,然後去推算你需要的所有其他東西。例如:如果你在建一個TODO列表,那就僅僅在這TODO items周圍儲存一個數組;不要為了數組長度去儲存一個state,如果需要,就去取數組長度。
想一下我們應用中的所有資料片段。我們有:
1.最原始的產品列表
2.使用者輸入的搜尋文本
3.checkbox的值
4.被篩選的產品
我們挨個看一下,找出哪一個是state。對於每一個資料片段提三個問題:
1.這是從父輩傳過來的props嗎?如果是,則它不是state。
2.它即時在改變嗎?如果不是,則不是state。
3.你能在組件中通過其他的state或者props得出它來嗎?如果能,則不是state。
最原始的產品清單是被當做props傳遞的,它不是state。搜尋文本和checkbox看起來像是state,因為它們即時在變而且不能通過其它資料得出。最後,被篩選的產品列表不是state,因為它能夠通過搜尋文本,checkbox和原始產品清單得出。
所以,我們的state就是:搜尋文本和checkbox的值。
Step 4: Identify where your state should live(確認你的state應該放在哪)
好的,這樣我們就確認了應用中state的最小單位(set)。下一步 ,我們需要確認哪一個組件改變或者擁有這個state。請記住,react在我們的組件層次中是單向資料流動的。可能不會一下就看明白哪個組件擁有哪個state。這一點對新手來說經常是最有挑戰性的部分,所以依據以下步驟可以弄明白:
對於你的應用中每一個state:
-確認每一個依靠這個state去渲染sth的組件
-找到一個組件共同擁有者(在組件層次中,位於所有組件上層的單獨組件也需要這個state)
-不管是組件共同擁有者還是組件層中的上層組件都應該擁有這個state
-如果你找不到一個擁有這個state的組件,那就建立一個功能只是儲存這個state的組件,把它放到組件共同擁有者的上方。
我們在當前的應用中試一下上邊的策略。
-ProductTable需要依據state去篩選產品列表,SearchBar展示搜尋文本和checkbox狀態也需要state。
-組件共同擁有者是FilterableProductTable.
-把filter text和checked value放在FilterableProductTable裡邊從概念上來說是行的通的。
Cool,我們就這樣愉快地決定了把state放在FilterableProductTable裡邊。首先,在FilterableProductTable裡添加一個getInitialState()方法,返回值是{filterText: ‘‘, inStockOnly: false},反映了應用的初始state。然後,把filterText和inStockOnly當做prop傳入ProductTable和SearchBar中。最後,用這些props過濾ProductTable中的rows,設定SearchBar中的
表單欄位的值。
現在你就能看到你的應用是如何啟動並執行:將filterText設為“ball”然後重新整理應用,你就能看到資料表正確更新了。
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'));
STEP 5:Add inverse data flow(添加反向資料流)
到這裡,我們建立了一個能夠根據state和prop在模組層間的流動去正確渲染的應用。現在是時候去支援資料的另一種方式流動:在深層次中的表單組件需要在FilterableProductTable裡去更新state。React中的這種資料流動非常明確清晰,所以能夠非常容易得理解你的應用是怎樣工作的,但是這需要比通常的雙向資料繫結寫更多的代碼。React提供了額外的一個叫做ReactLink的工具讓這種模式變得像雙向資料繫結一樣的舒服,這樣的目的就是,讓所有的事情變得更明確。
如果你嘗試著去在最近版本範例裡去寫代碼或檢查checkbox,你會看到React忽略了你的輸入。這是有意為之的,因為我們設定了input的prop一直是和FilterableProductTable傳過來的state相同的。 我們來想一下我們想要什麼樣的事情發生。我們想確保無論使用者什麼時候改變表單,我們就去更新state以便反映出使用者的輸入。既然組件應該僅僅更新他們自己的state,FilterableProductTable會傳遞給SearchBar回呼函數,在state需要更新的時候就會調用。我們可以在input上使用onchange事件去通知調用callback。被FilterableProductTable傳遞過來的callback會調用setState(),然後應用就會被更新了。
儘管這聽起來很複雜,但是真的只是需要幾行代碼。並且你的應用中資料的流動過程會非常的清晰。And that‘s it。
但願這篇文章能夠協助你弄清楚怎麼用React去建立組件和應用。儘管這要比平時多寫一些代碼,但是請記住代碼的可讀性的重要性要遠遠超過代碼的書寫,閱讀這些模組化和非常清晰的代碼是非常容易的。當你開始建立大型的組件庫,你就會感謝這種明確性和模組化,隨著代碼的複用,你的代碼量就會開始減少了。
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'));
------------------------------------------------我是分隔字元--------------------------------------------------
原文結束。
本文主要是依據一個產品標的例子來介紹React的單向資料流的思想,從一開始怎樣分析網站模型的結構,怎樣劃分組件,組件中的資料,資料之間的關係,根據這些關係,有固定的方法來協助我們決定哪一些資料屬於state,哪一些是prop,確定了state之後,就可以給state依存的組件添加監聽方法,監聽input輸入的變化,從而即時更新資料模型,重新渲染介面。
Thinking in React(翻譯)