[译] React Components, Elements, and Instances

管理实例

在传统的 UI 模型中,由你管理子组件实例的创建销毁。如果有个 From 想要渲染一个 Button 组件,它需要创建它的实例,并手动根据新的数据更新。

class Form extends TraditionalObjectOrientedView {
  render() {
    // Read some data passed to the view
    const { isSubmitted, buttonText } = this.attrs;

    if (!isSubmitted && !this.button) {
      // Form is not yet submitted. Create the button!
      this.button = new Button({
        children: buttonText,
        color: 'blue'
      });
      this.el.appendChild(this.button.el);
    }

    if (this.button) {
      // The button is visible. Update its text!
      this.button.attrs.children = buttonText;
      this.button.render();
    }

    if (isSubmitted && this.button) {
      // Form was submitted. Destroy the button!
      this.el.removeChild(this.button.el);
      this.button.destroy();
    }

    if (isSubmitted && !this.message) {
      // Form was submitted. Show the success message!
      this.message = new Message({ text: 'Success!' });
      this.el.appendChild(this.message.el);
    }
  }
}

每一个组件的实例都需要保存它的 DOM 节点以及子组件的实例,并在合适的时间创建、更新、销毁它们。代码行数随着组件可能的状态增多增长的很快,并且父组件可以直接访问子组件的实例,使得未来很难将它们解耦。

那么 React 有什么不同呢?


使用 Element 描述树状结构

Element 是描述组件实例或者 DOM 节点及其属性的 plain 对象。一个 Element 并不是实例,不能从它调用任何方法。它有两个字段 type: (string | ReactClass)props: Object

DOM Elements

当 element 的 type 为字符串时,它表示一个 DOM 节点。

{
  type: 'button',
  props: {
    className: 'button button-blue',
    children: {
      type: 'b',
      props: {
        children: 'OK!'
      }
    }
  }
}

Component Elements

当 element 的 type 为函数或类时,它表示一个 React 组件。

{
  type: Button,
  props: {
    color: 'blue',
    children: 'OK!'
  }
}

就像描述 DOM 节点,描述组件的 element 依旧是 element。它们之间可以相互嵌套混合。


组件封装 Element 树

当 React 看到 element 的 type 是函数或类时,它会询问组件要渲染什么 element,给它对应的 props

例如如下 element:

{
  type: Button,
  props: {
    color: 'blue',
    children: 'OK!'
  }
}

React 会询问 Button 它要渲染什么,而 Button 会返回如下 element:

{
  type: 'button',
  props: {
    className: 'button button-blue',
    children: {
      type: 'b',
      props: {
        children: 'OK!'
      }
    }
  }
}

React 会重复这个过程直到获得每个组件下面的 DOM 标签。

返回的 element 树可以包含描述 DOM 节点的 element 和描述其他组件的 element。这使您可以在不依赖内部 DOM 结构的情况下编写独立的 UI 部分。

我们让 React 创建、更新和销毁实例,我们使用从组件返回的 element 来描述它们,而 React 负责管理这些实例。


组件可以是类或函数

有三种大致等价的声明组件的方式:

// 1) 作为接受 props 的函数
const Button = ({ children, color }) => ({
  type: 'button',
  props: {
    className: 'button button-' + color,
    children: {
      type: 'b',
      props: {
        children: children
      }
    }
  }
});

// 2) 使用 React.createClass() 工厂函数
const Button = React.createClass({
  render() {
    const { children, color } = this.props;
    return {
      type: 'button',
      props: {
        className: 'button button-' + color,
        children: {
          type: 'b',
          props: {
            children: children
          }
        }
      }
    };
  }
});

// 3) 使用 ES6 class 继承自 React.Component
class Button extends React.Component {
  render() {
    const { children, color } = this.props;
    return {
      type: 'button',
      props: {
        className: 'button button-' + color,
        children: {
          type: 'b',
          props: {
            children: children
          }
        }
      }
    };
  }
}

使用 class 来定义组件时,它比函数式组件更强大。它可以保存一些本地状态,并在对应 DOM 节点创建销毁时执行自定义的逻辑。

无论函数或类,它们本质上都是 React 组件。它们接受 props 作为输入,返回 element 作为输出。


由上而下 Reconciliation

调用:

ReactDOM.render({
  type: Form,
  props: {
    isSubmitted: false,
    buttonText: 'OK!'
  }
}, document.getElementById('root'));

React 会给 Form 组件 props,获得返回的 element 树。它将逐渐 refine 它对组件树的理解,以简单的原语表示。

{
  type: Form,
  props: {
    isSubmitted: false,
    buttonText: 'OK!'
  }
}

// 从 From 找到 Button
{
  type: Button,
  props: {
    children: 'OK!',
    color: 'blue'
  }
}

// 从 Button 找到 button
{
  type: 'button',
  props: {
    className: 'button button-blue',
    children: {
      type: 'b',
      props: {
        children: 'OK!'
      }
    }
  }
}

这部分处理被 React 称作 reconciliation,它在 ReactDOM.render()setState() 时执行。reconciliation 结束后,React 获知最终的 DOM 树,像 react-domreact-native 那样的 render 会采取最小的改动来更新 DOM 节点(或者 React Native 那样特定平台的视图)。

这个逐渐 refining 的过程也是 React 应用易于优化的原因。如果组件树的某些部分变得太大而无法高效地被 React 访问,那么你可以告诉它如果相关的 props 没有改变,就跳过这个 refiningdiffing。如果 props 是不可变(immutable)的,计算 props 是否改变的速度非常快。所以可以同时使用 React 和 immutability,并且可以用最小的工作提供很大的优化。

只有类组件有实例,并且你不应该直接创建它们,React 为你做了这些。虽然父组件实例访问子组件实例的机制存在,但它们仅用于必要的操作(例如设置焦点在字段上),通常应避免。

React 负责为每个类组件创建一个实例,因此可以用方法和本地状态以面向对象的方式编写组件,但除此之外,实例在React的编程模型中并不是非常重要,并且由 React 本身来管理。


总结

element 是描述 DOM 节点或其它组件的 plain 对象。elememt 可以在 props 中包含其它 elememt。创建 element 很容易。一旦创建后它不可修改。

一个组件可以用几种不同的方式来声明。它可以是一个带有 render() 方法的类。在简单的情况下,它可以被定义为一个函数。无论哪种情况,都需要 props 作为输入,并返回一个 elememt 树作为输出。

一个实例就是在类组件中用 this 引用的。这对于存储本地状态和对生命周期事件做出反应非常有用。

函数式组件没有实例,类组件有实例。

创建 elements 要使用 React.createElement()JSX 或者一个 element 工厂函数。不要在实际代码中用纯对象写 elements。


延伸阅读


注:由于安全原因,所有的 React emelents 需要一个额外的 $$typeof: Symbol.for('react.element') 字段定义在对象上。


React Blog: React Components, Elements, and Instances