鍍金池/ 教程/ HTML/ 復(fù)合組件
顯示數(shù)據(jù)
組件的引用
Controlled Input 值為 null 的情況
Reconciliation
子 props 的類型
組件的詳細(xì)說明和生命周期
傳遞 Props
特殊的非 DOM 屬性
組件 API
PureRenderMixin
雙向綁定輔助工具
瀏覽器中的工作原理
深入 JSX
表單組件
Dangerously Set innerHTML
入門
JSX 中的 If-Else
克隆組件
教程
更多的關(guān)于Refs
JSX 的 false 處理
高級(jí)性能
Mounting 后 componentWillReceiveProps 未被觸發(fā)
簡介
測試工具集
JSX 陷阱
工具集成(ToolingIntegration)
公開組件功能
通過 AJAX 加載初始數(shù)據(jù)
事件系統(tǒng)
可復(fù)用組件
this.props.children undefined
不可變數(shù)據(jù)的輔助工具(Immutability Helpers)
動(dòng)態(tài)交互式用戶界面
組件的 DOM 事件監(jiān)聽
復(fù)合組件
動(dòng)畫
插件
JSX 展開屬性
行內(nèi)樣式
性能分析工具
類名操作
與其他類庫并行使用 React
鍵控的片段
標(biāo)簽和屬性支持
組件間的通信
React (虛擬)DOM 術(shù)語
JSX 根節(jié)點(diǎn)的最大數(shù)量
在樣式props中快速制定像素值
頂層 API
深入理解 React
自閉合標(biāo)簽
為什么使用 React?
getInitialState 里的 Props 是一個(gè)反模式
與 DOM 的差異

復(fù)合組件

目前為止,我們已經(jīng)學(xué)了如何用單個(gè)組件來展示數(shù)據(jù)和處理用戶輸入。下一步讓我們來體驗(yàn) React 最激動(dòng)人心的特性之一:可組合性(composability)。

動(dòng)機(jī):關(guān)注分離

通過復(fù)用那些接口定義良好的組件來開發(fā)新的模塊化組件,我們得到了與使用函數(shù)和類相似的好處。具體來說就是能夠通過開發(fā)簡單的組件把程序的不同關(guān)注面分離。如果為程序開發(fā)一套自定義的組件庫,那么就能以最適合業(yè)務(wù)場景的方式來展示你的用戶界面。

組合實(shí)例

一起來使用 Facebook Graph API 開發(fā)顯示個(gè)人圖片和用戶名的簡單 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')
);

從屬關(guān)系

上面例子中,Avatar 擁有 ProfilePicProfileLink 的實(shí)例。擁有者 就是給其它組件設(shè)置 props 的那個(gè)組件。更正式地說, 如果組件 Yrender() 方法是創(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)系。在上一個(gè)例子中,Avatar 擁有 div、ProfilePicProfileLink 實(shí)例,divProfilePicProfileLink 實(shí)例的父級(jí)(但不是擁有者)。

子級(jí)

實(shí)例化 React 組件時(shí),你可以在開始標(biāo)簽和結(jié)束標(biāo)簽之間引用在 React 組件或者 Javascript 表達(dá)式:

<Parent><Child /></Parent>

Parent 能通過專門的 this.props.children props 讀取子級(jí)。this.props.children 是一個(gè)不透明的數(shù)據(jù)結(jié)構(gòu): 通過 React.Children 工具類來操作。

子級(jí)校正(Reconciliation)

校正就是每次 render 方法調(diào)用后 React 更新 DOM 的過程。 一般情況下,子級(jí)會(huì)根據(jù)它們被渲染的順序來做校正。例如,下面代碼描述了兩次渲染的過程:

// 第一次渲染
<Card>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</Card>
// 第二次渲染
<Card>
  <p>Paragraph 2</p>
</Card>

直觀來看,只是刪除了<p>Paragraph 1</p>。事實(shí)上,React 先更新第一個(gè)子級(jí)的內(nèi)容,然后刪除最后一個(gè)組件。React 是根據(jù)子級(jí)的順序來校正的。

子組件狀態(tài)管理

對于大多數(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>

動(dòng)態(tài)子級(jí)

如果子組件位置會(huì)改變(如在搜索結(jié)果中)或者有新組件添加到列表開頭(如在流中)情況會(huì)變得更加復(fù)雜。如果子級(jí)要在多個(gè)渲染階段保持自己的特征和狀態(tài),在這種情況下,你可以通過給子級(jí)設(shè)置惟一標(biāo)識(shí)的 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 的子級(jí)時(shí),它會(huì)確保它們被重新排序(而不是破壞)或者刪除(而不是重用)。 務(wù)必key 添加到子級(jí)數(shù)組里組件本身上,而不是每個(gè)子級(jí)內(nèi)部最外層 HTML 上:

// 錯(cuò)誤!
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 的子級(jí)。object 的 key 會(huì)被當(dāng)作每個(gè)組件的 key。但是一定要牢記 JavaScript 并不總是保證屬性的順序會(huì)被保留。實(shí)際情況下瀏覽器一般會(huì)保留屬性的順序,除了 使用 32 位無符號(hào)數(shù)字做為 key 的屬性。數(shù)字型屬性會(huì)按大小排序并且排在其它屬性前面。一旦發(fā)生這種情況,React 渲染組件的順序就是混亂??赡茉?key 前面加一個(gè)字符串前綴來避免:

  render: function() {
    var items = {};

    this.props.results.forEach(function(result) {
      // 如果 result.id 看起來是一個(gè)數(shù)字(比如短哈希),那么
      // 對象字面量的順序就得不到保證。這種情況下,需要添加前綴
      // 來確保 key 是字符串。
      items['result-' + result.id] = <li>{result.text}</li>;
    });

    return (
      <ol>
        {items}
      </ol>
    );
  }

數(shù)據(jù)流

React 里,數(shù)據(jù)通過上面介紹過的 props 從擁有者流向歸屬者。這就是高效的單向數(shù)據(jù)綁定(one-way data binding):擁有者通過它的 propsstate 計(jì)算出一些值,并把這些值綁定到它們擁有的組件的 props 上。因?yàn)檫@個(gè)過程會(huì)遞歸地調(diào)用,所以數(shù)據(jù)變化會(huì)自動(dòng)在所有被使用的地方自動(dòng)反映出來。

性能提醒

你或許會(huì)擔(dān)心如果一個(gè)擁有者有大量子級(jí)時(shí),對于數(shù)據(jù)變化做出響應(yīng)非常耗費(fèi)性能。值得慶幸的是執(zhí)行 JavaScript 非常的快,而且 render() 方法一般比較簡單,所以在大部分應(yīng)用里這樣做速度極快。此外,性能的瓶頸大多是因?yàn)?DOM 更新,而非 JS 執(zhí)行,而且 React 會(huì)通過批量更新和變化檢測來優(yōu)化性能。

但是,有時(shí)候需要做細(xì)粒度的性能控制。這種情況下,可以重寫 shouldComponentUpdate() 方法返回 false 來讓 React 跳過對子樹的處理。參考 React reference docs 了解更多。

注意:

如果在數(shù)據(jù)變化時(shí)讓 shouldComponentUpdate() 返回 false,React 就不能保證用戶界面同步。當(dāng)使用它的時(shí)候一定確保你清楚到底做了什么,并且只在遇到明顯性能問題的時(shí)候才使用它。不要低估 JavaScript 的速度,DOM 操作通常才是慢的原因。