• 复合组件
    • 动机:关注分离
    • 组合实例
    • 从属关系
    • 子级
      • 子级校正(Reconciliation)
      • 子组件状态管理
      • 动态子级
    • 数据流
    • 性能提醒

    复合组件

    目前为止,我们已经学了如何用单个组件来展示数据和处理用户输入。下一步让我们来体验 React 最激动人心的特性之一:可组合性(composability)。

    动机:关注分离

    通过复用那些接口定义良好的组件来开发新的模块化组件,我们得到了与使用函数和类相似的好处。具体来说就是能够通过开发简单的组件把程序的不同关注面分离。如果为程序开发一套自定义的组件库,那么就能以最适合业务场景的方式来展示你的用户界面。

    组合实例

    一起来使用 Facebook Graph API 开发显示个人图片和用户名的简单 Avatar 组件吧。

    1. var Avatar = React.createClass({
    2. render: function() {
    3. return (
    4. <div>
    5. <ProfilePic username={this.props.username} />
    6. <ProfileLink username={this.props.username} />
    7. </div>
    8. );
    9. }
    10. });
    11. var ProfilePic = React.createClass({
    12. render: function() {
    13. return (
    14. <img src={'https://graph.facebook.com/' + this.props.username + '/picture'} />
    15. );
    16. }
    17. });
    18. var ProfileLink = React.createClass({
    19. render: function() {
    20. return (
    21. <a href={'https://www.facebook.com/' + this.props.username}>
    22. {this.props.username}
    23. </a>
    24. );
    25. }
    26. });
    27. ReactDOM.render(
    28. <Avatar username="pwh" />,
    29. document.getElementById('example')
    30. );

    从属关系

    上面例子中,Avatar 拥有 ProfilePicProfileLink 的实例。拥有者 就是给其它组件设置 props 的那个组件。更正式地说,如果组件 Yrender() 方法是创建了组件 X,那么 Y 就拥有 X。上面讲过,组件不能修改自身的 props - 它们总是与它们拥有者设置的保持一致。这是保持用户界面一致性的基本不变量。

    把从属关系与父子关系加以区别至关重要。从属关系是 React 特有的,而父子关系简单来讲就是DOM 里的标签的关系。在上一个例子中,Avatar 拥有 divProfilePicProfileLink 实例,divProfilePicProfileLink 实例的父级(但不是拥有者)。

    子级

    实例化 React 组件时,你可以在开始标签和结束标签之间引用在React 组件或者Javascript 表达式:

    1. <Parent><Child /></Parent>

    Parent 能通过专门的 this.props.children props 读取子级。this.props.children 是一个不透明的数据结构: 通过 React.Children 工具类 来操作。

    子级校正(Reconciliation)

    校正就是每次 render 方法调用后 React 更新 DOM 的过程。 一般情况下,子级会根据它们被渲染的顺序来做校正。例如,下面代码描述了两次渲染的过程:

    1. // 第一次渲染
    2. <Card>
    3. <p>Paragraph 1</p>
    4. <p>Paragraph 2</p>
    5. </Card>
    6. // 第二次渲染
    7. <Card>
    8. <p>Paragraph 2</p>
    9. </Card>

    直观来看,只是删除了<p>Paragraph 1</p>。事实上,React 先更新第一个子级的内容,然后删除最后一个组件。React 是根据子级的顺序来校正的。

    子组件状态管理

    对于大多数组件,这没什么大碍。但是,对于使用 this.state 来在多次渲染过程中里维持数据的状态化组件,这样做潜在很多问题。

    多数情况下,可以通过隐藏组件而不是删除它们来绕过这些问题。

    1. // 第一次渲染
    2. <Card>
    3. <p>Paragraph 1</p>
    4. <p>Paragraph 2</p>
    5. </Card>
    6. // 第二次渲染
    7. <Card>
    8. <p style={{'{{'}}display: 'none'}}>Paragraph 1</p>
    9. <p>Paragraph 2</p>
    10. </Card>

    动态子级

    如果子组件位置会改变(如在搜索结果中)或者有新组件添加到列表开头(如在流中)情况会变得更加复杂。如果子级要在多个渲染阶段保持自己的特征和状态,在这种情况下,你可以通过给子级设置惟一标识的 key 来区分。

    1. render: function() {
    2. var results = this.props.results;
    3. return (
    4. <ol>
    5. {results.map(function(result) {
    6. return <li key={result.id}>{result.text}</li>;
    7. })}
    8. </ol>
    9. );
    10. }

    当 React 校正带有 key 的子级时,它会确保它们被重新排序(而不是破坏)或者删除(而不是重用)。
    务必key 添加到子级数组里组件本身上,而不是每个子级内部最外层 HTML 上:

    1. // 错误!
    2. var ListItemWrapper = React.createClass({
    3. render: function() {
    4. return <li key={this.props.data.id}>{this.props.data.text}</li>;
    5. }
    6. });
    7. var MyComponent = React.createClass({
    8. render: function() {
    9. return (
    10. <ul>
    11. {this.props.results.map(function(result) {
    12. return <ListItemWrapper data={result}/>;
    13. })}
    14. </ul>
    15. );
    16. }
    17. });
    1. // 正确 :)
    2. var ListItemWrapper = React.createClass({
    3. render: function() {
    4. return <li>{this.props.data.text}</li>;
    5. }
    6. });
    7. var MyComponent = React.createClass({
    8. render: function() {
    9. return (
    10. <ul>
    11. {this.props.results.map(function(result) {
    12. return <ListItemWrapper key={result.id} data={result}/>;
    13. })}
    14. </ul>
    15. );
    16. }
    17. });

    也可以传递ReactFragment 对象 来做有 key 的子级。详见Keyed Fragments

    数据流

    React 里,数据通过上面介绍过的 props 从拥有者流向归属者。这就是高效的单向数据绑定(one-way data binding):拥有者通过它的 propsstate 计算出一些值,并把这些值绑定到它们拥有的组件的 props 上。因为这个过程会递归地调用,所以数据变化会自动在所有被使用的地方自动反映出来。

    性能提醒

    你或许会担心如果一个拥有者有大量子级时,对于数据变化做出响应非常耗费性能。值得庆幸的是执行 JavaScript 非常的快,而且 render() 方法一般比较简单,所以在大部分应用里这样做速度极快。此外,性能的瓶颈大多是因为 DOM 更新,而非 JS 执行,而且 React 会通过批量更新和变化检测来优化性能。

    但是,有时候需要做细粒度的性能控制。这种情况下,可以重写 shouldComponentUpdate() 方法返回 false 来让 React 跳过对子树的处理。参考 React reference docs 了解更多。

    注意:

    如果在数据变化时让 shouldComponentUpdate() 返回 false,React 就不能保证用户界面同步。当使用它的时候一定确保你清楚到底做了什么,并且只在遇到明显性能问题的时候才使用它。不要低估 JavaScript 的速度,DOM 操作通常才是慢的原因。