目前為止,我們已經(jīng)學(xué)了如何用單個組件來展示數(shù)據(jù)和處理用戶輸入。下一步讓我們來體驗 React 最激動人心的特性之一:可組合性(composability)。
通過復(fù)用那些接口定義良好的組件來開發(fā)新的模塊化組件,我們得到了與使用函數(shù)和類相似的好處。具體來說就是能夠通過開發(fā)簡單的組件把程序的不同關(guān)注面分離。如果為程序開發(fā)一套自定義的組件庫,那么就能以最適合業(yè)務(wù)場景的方式來展示你的用戶界面。
一起來使用 Facebook Graph API 開發(fā)顯示個人圖片和用戶名的簡單 Avatar 組件吧。
var Avatar = React.createClass({ render: function() { return ( <div> <ProfilePic username={this.props.username} /> <ProfileLink username={this.props.username} /> </div> ); } });var ProfilePic = React.createClass({ render: function() { return ( <img src={'http://graph.facebook.com/' + this.props.username + '/picture'} /> ); } });var ProfileLink = React.createClass({ render: function() { return ( <a href={'http://www.facebook.com/' + this.props.username}> {this.props.username} </a> ); } }); React.render( <Avatar username="pwh" />, document.getElementById('example') );
上面例子中,Avatar
擁有 ProfilePic
和 ProfileLink
的實例。擁有者
就是給其它組件設(shè)置 props
的那個組件。更正式地說, 如果組件 Y
在render()
方法是創(chuàng)建了組件 X
,那么 Y
就擁有 X
。上面講過,組件不能修改自身的 props
- 它們總是與它們擁有者設(shè)置的保持一致。這是保持用戶界面一致性的關(guān)鍵性原則。
把從屬關(guān)系與父子關(guān)系加以區(qū)別至關(guān)重要。從屬關(guān)系是 React 特有的,而父子關(guān)系簡單來講就是 DOM 里的標(biāo)簽的關(guān)系。在上一個例子中,Avatar
擁有div
、ProfilePic
和 ProfileLink
實例,div
是 ProfilePic
和 ProfileLink
實例的父級(但不是擁有者)。
實例化 React 組件時,你可以在開始標(biāo)簽和結(jié)束標(biāo)簽之間引用在 React 組件或者 Javascript 表達式:
<Parent><Child /></Parent>
Parent
能通過專門的 this.props.children
props 讀取子級。this.props.children
是一個不透明的數(shù)據(jù)結(jié)構(gòu): 通過 React.Children 工具類來操作。
校正就是每次 render 方法調(diào)用后 React 更新 DOM 的過程。 一般情況下,子級會根據(jù)它們被渲染的順序來做校正。例如,下面代碼描述了兩次渲染的過程:
// 第一次渲染<Card> <p>Paragraph 1</p> <p>Paragraph 2</p></Card>// 第二次渲染<Card> <p>Paragraph 2</p></Card>
直觀來看,只是刪除了<p>Paragraph 1</p>
。事實上,React 先更新第一個子級的內(nèi)容,然后刪除最后一個組件。React 是根據(jù)子級的順序來校正的。
對于大多數(shù)組件,這沒什么大礙。但是,對于使用 this.state
來在多次渲染過程中里維持?jǐn)?shù)據(jù)的狀態(tài)化組件,這樣做潛在很多問題。
多數(shù)情況下,可以通過隱藏組件而不是刪除它們來繞過這些問題。
// 第一次渲染<Card> <p>Paragraph 1</p> <p>Paragraph 2</p></Card>// 第二次渲染<Card> <p style={{'{{'}}display: 'none'}}>Paragraph 1</p> <p>Paragraph 2</p></Card>
如果子組件位置會改變(如在搜索結(jié)果中)或者有新組件添加到列表開頭(如在流中)情況會變得更加復(fù)雜。如果子級要在多個渲染階段保持自己的特征和狀態(tài),在這種情況下,你可以通過給子級設(shè)置惟一標(biāo)識的 key
來區(qū)分。
render: function() { var results = this.props.results; return ( <ol> {results.map(function(result) { return <li key={result.id}>{result.text}</li>; })} </ol> ); }
當(dāng) React 校正帶有 key 的子級時,它會確保它們被重新排序(而不是破壞)或者刪除(而不是重用)。 務(wù)必
把 key
添加到子級數(shù)組里組件本身上,而不是每個子級內(nèi)部最外層 HTML 上:
// 錯誤!var ListItemWrapper = React.createClass({ render: function() { return <li key={this.props.data.id}>{this.props.data.text}</li>; } });var MyComponent = React.createClass({ render: function() { return ( <ul> {this.props.results.map(function(result) { return <ListItemWrapper data={result}/>; })} </ul> ); } });// 正確 :)var ListItemWrapper = React.createClass({ render: function() { return <li>{this.props.data.text}</li>; } });var MyComponent = React.createClass({ render: function() { return ( <ul> {this.props.results.map(function(result) { return <ListItemWrapper key={result.id} data={result}/>; })} </ul> ); } });
也可以傳遞 object 來做有 key 的子級。object 的 key 會被當(dāng)作每個組件的 key
。但是一定要牢記 JavaScript 并不總是保證屬性的順序會被保留。實際情況下瀏覽器一般會保留屬性的順序,除了 使用 32 位無符號數(shù)字做為 key 的屬性。數(shù)字型屬性會按大小排序并且排在其它屬性前面。一旦發(fā)生這種情況,React 渲染組件的順序就是混亂??赡茉?key 前面加一個字符串前綴來避免:
render: function() { var items = {}; this.props.results.forEach(function(result) { // 如果 result.id 看起來是一個數(shù)字(比如短哈希),那么 // 對象字面量的順序就得不到保證。這種情況下,需要添加前綴 // 來確保 key 是字符串。 items['result-' + result.id] = <li>{result.text}</li>; }); return ( <ol> {items} </ol> ); }
React 里,數(shù)據(jù)通過上面介紹過的 props
從擁有者流向歸屬者。這就是高效的單向數(shù)據(jù)綁定(one-way data binding):擁有者通過它的 props
或state
計算出一些值,并把這些值綁定到它們擁有的組件的 props 上。因為這個過程會遞歸地調(diào)用,所以數(shù)據(jù)變化會自動在所有被使用的地方自動反映出來。
你或許會擔(dān)心如果一個擁有者有大量子級時,對于數(shù)據(jù)變化做出響應(yīng)非常耗費性能。值得慶幸的是執(zhí)行 JavaScript 非常的快,而且 render()
方法一般比較簡單,所以在大部分應(yīng)用里這樣做速度極快。此外,性能的瓶頸大多是因為 DOM 更新,而非 JS 執(zhí)行,而且 React 會通過批量更新和變化檢測來優(yōu)化性能。
但是,有時候需要做細粒度的性能控制。這種情況下,可以重寫 shouldComponentUpdate()
方法返回 false 來讓 React 跳過對子樹的處理。參考 React reference docs 了解更多。
注意:
如果在數(shù)據(jù)變化時讓
shouldComponentUpdate()
返回 false,React 就不能保證用戶界面同步。當(dāng)使用它的時候一定確保你清楚到底做了什么,并且只在遇到明顯性能問題的時候才使用它。不要低估 JavaScript 的速度,DOM 操作通常才是慢的原因。
更多建議: