React 组件,元素和实例

来源:开源中国社区 作者:oschina
  

组件,实例和元素之间的不同点困扰了很多 React 初学者。为什么会有三个不同的术语指向屏幕的绘图。

管理实例

 

如果你是 React 新手,也许你以前只使用过组件类和实例。例如你也许会通过创建一个类的方式声明一个 Button 组件。当 App 运行的时候,你也许会在屏幕上创建几个 Button 组件的引用,每个引用都有自己的属性和本地状态。这是传统的面向对象 UI 程序。那为什么会有元素呢?

在这个传统的 UI 模块里,由你来管理子组件实例的创建和销毁。如果一个 Form 组件想渲染一个 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);
    }
  }
}

这虽然是伪代码,但是通过一个面向对象的方式使用像 BackBone 这样库时,你最终还是或多或少的像通常那样写一个复合 UI 代码。

每一个组件都得保留到其DOM节点还有其子组件实例的引用,并且在正确时间对它们进行创建、更新和销毁操作。代码行数量的增长是潜在的组件语句数量的平方,而父组件得直接访问它们的子组件实例,使得将来要对它们进行解耦会很困难。

React 如何不同了呢?

元素来描述树

在React中,由元素来解决问题。元素就是一个单纯的对象,它描述了一个组件实例或者DOM节点机器需要的属性。 它只包含有关于组件类型(例如:一个Button),组件的属性(例如:它的颜色),以及它里面的子元素这些信息。

元素并不是一个实在的实例,而是一种告诉React你想要在屏幕上看到什么东西的方式。你不能在元素上面调用方法。它只是一个不可修改的描述性对象,带有两个属性域:type:string|ReactClass)和props:Object1 。

 DOM 元素
 

当元素的类型为string时,就用它来代表以元素type值作为标记名称、以props值作为其属性的DOM节点。这就是React将会渲染的东西。例如:

 

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


 


 

 

这个元素只是一种将下面的HTML表示为一个纯对象的方式:

<button class='button button-blue'>
  <b>
    OK!
  </b>
</button>


 


 

注意元素是如何被嵌套的。根据约定,当我们想要常见一个元素树时,我们会将一个或者更多个子元素指定为它们容器元素的children属性。

重要的是子元素和父元素都只是描述性的,而不是实实在在的实例。当你创建它们时,它们并不代表屏幕上的任何东西。你可以将它们创建出来然后仍在一旁,并没有什么关系。

React 元素容易移动,不需要做什么转换,当然也不实际的DOM元素更轻量——它们都只是对象而已啦!

    组件元素
 

    不过,元素的类型也可以是一个对应到React组件的函数或者类:

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

    这是  React  的核心概念。

描述一个组件的元素还是一个元素,就跟一个描述DOM节点的元素没什么两样。它们可以相互嵌套和混合:

 这个特性然你可以用一个带有特殊的 color 属性的Button来定义一个DangerButton,不用担心Button会被渲染成一个DOM(button)、一个<div>还是别的什么东西:

const DangerButton = ({ children }) => ({
  type: Button,
  props: {
    color: 'red',
    children: children
  }
});

你可以在一个单独的元素中混合并匹配DOM和组件元素:

const DeleteAccount = () => ({
  type: 'div',
  props: {
    children: [{
      type: 'p',
      props: {
        children: 'Are you sure?'
      }
    }, {
      type: DangerButton,
      props: {
        children: 'Yep'
      }
    }, {
      type: Button,
      props: {
        color: 'blue',
        children: 'Cancel'
      }
   }]
});

或者如果你选择了JSX的话 JSX:

const DeleteAccount = () => (
  <div>
    <p>Are you sure?</p>
    <DangerButton>Yep</DangerButton>
    <Button color='blue'>Cancel</Button>
  </div>
);

这样的混合和匹配有助于组件同其它东西的解耦,因为他们可以通过组合同时表达出is-a和has-a的关系:

  • Button 是一个带有特殊属性的DOM <button> 。

  • DangerButton 带有特殊属性的Button。

  • DeleteAccount 在一个<div>中包含一个Button和一个DangerButton.

组件封装了元素树
 

当 React 发现了一个带有函数或者类类型的元素,它就会知道要根据提供的对应 props 请求向那个组件请求其所渲染的元素。

当它发现这个元素时:

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


 


 

React 会向 Button 请求其所渲染的东西。Button 将会返回这个元素:

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


 


 

React 会重复这一过程,知道它已经知悉了页面上每个组件所依赖的 DOM 标记元素。

React 就像是一个小孩,会针对每个“X 是 Y”的命题提出“什么是 Y”这种问题,要你来做出解释,直到他们搞明白了事情的所有细节。

还记得上面的 From 示例吗?它可以用 React 写成下面这个样子1

const Form = ({ isSubmitted, buttonText }) => {
  if (isSubmitted) {
    // Form submitted! Return a message element.
    return {
      type: Message,
      props: {
        text: 'Success!'
      }
    };
  }

  // Form is still visible! Return a button element.
  return {
    type: Button,
    props: {
      children: buttonText,
      color: 'blue'
    }
  };
};


 


 

这就是了! 对于一个 React 组件而言,props 是输入,而一个元素树就是输出。

所返回的元素树可以同时包含描述了 DOM 节点的元素,以及描述了其它组件的元素。这样就让你能将 UI 的各个独立部分组装起来,而不用去依赖于它们的内部 DOM 结构。

我们让 React 来创建、更新和销毁实例,用元素来描述它们并将它们从组件那里返回,而 React 会负责管理这些实例。

组件可以是类或者函数
 

在上述的代码中, Form, Message, 和 Button 都是 React 组件。他们可以想上面那样写成函数, 也可以是继承自 React.component 的类。这里这三种声明组件的方式几乎是对等的:

// 1) As a function of props
const Button = ({ children, color }) => ({
  type: 'button',
  props: {
    className: 'button button-' + color,
    children: {
      type: 'b',
      props: {
        children: children
      }
    }
  }
});

// 2) Using the React.createClass() factory
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) As an ES6 class descending from 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
          }
        }
      }
    };
  }
}


 


 

当把组件定义成类是,会比一个函数式的组件更强大一些。它可以在对应的 DOM 节点创建或者销毁时存储一些本地状态并执行一些自定义的逻辑。

函数式组件功能较少不过更简单,使用一个 render() 方法的效果同类组件相似。除非你需要使用一种只在类中具备的功能特性,我们建议你还是使用函数式的组件。

不过,函数亦或者类,基本上他们都是 React 的组件,所以都是以 props 作为输入,以返回的元素作为输出。

自上而下的整合
 

当你像下面这样调用:

 

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


 


 

 

React 会根据这些 props 向 From 组件请求其返回的元素树。最终它会以一种相对更简单的原语来“提炼”其对你的组件树的理解:

// React: You told me this...
{
  type: Form,
  props: {
    isSubmitted: false,
    buttonText: 'OK!'
  }
}

// React: ...And Form told me this...
{
  type: Button,
  props: {
    children: 'OK!',
    color: 'blue'
  }
}

// React: ...and Button told me this! I guess I'm done.
{
  type: 'button',
  props: {
    className: 'button button-blue',
    children: {
      type: 'b',
      props: {
        children: 'OK!'
      }
    }
  }
}


 


 

这就是 React 中所谓整合过程的一部分,这一过程开始于你调用 ReactDOM.render() 或者 setState() 时。在整合的末尾,React 就知道了结果 DOM 树,还有一个像 react-dom 或者 react-native 这样的渲染器,应用了需要更新到 DOM 节点(或者是 React Native 环境下的平台独立的视图)上的必要修改的最小集合。

这种逐步提炼的过程也是 React 容易优化的原因所在。如果你的组件树的某些部分对于 React 的访问效率而言变得过大,你可以告诉他跳过这个“提炼”的过程并且比较这个数的特定部分是不是没有发生改变。如果其是不可修改,那么要 props 有没有发生改变会非常快,因此 React 和不可变的方法在一起能运作的很好,并且最少的工作量就能产生很棒的优化效果。

你可能已经注意到这篇博客的字里行间许多都是关于组建和元素的,而关于其实例并没有聊那么多。事实上,实例比起大多数面向对象 UI 框架中,其在 React 的重要性要小得多。

只有声明为类的组件拥有实例,而你从来不会去直接创建它们: React 会为你做这些事儿。而存在的父组件访问子组件实例的机制, 只被用于必要的操作(比如设置一个输入域的焦点),并且一般应该避免这样做。

React 会为每个类组件负责创建一个实例,因此你可以用一种面向对象的方法使用方法和本地状态编写组件,但除此之外,实例在 React 的编程模型中并不是特别重要,并且它是有 React 自身去管理的。

总结

元素时一个纯对象,描述了你想要在屏幕上显示的 DOM 节点或者其它组件。元素可以在他们的 props 中包含其它元素。创建一个 React 组件是廉价的。元素一旦被创建,就从不会被改变。

组件可以用几种不同的方式进行声明。它可以是一个带有 render() 方法的类。也可在简单的情况下被定义成一个函数。两种情况中它都是以 props 作为输入,以返回的元素树作为输出。

当组件会接收一些 props 作为输入,这是因为一个特定的父组件会返回一个带有其类型和 props 的元素。这就是为什么人们会说在 React 中 props 会单项流转: 从父元素到子元素。

实例是你在你写的组件类中使用 this 表示的东西,这在存储本地状态和对生命周期内的事件做出反应中是有用的。

函数式组件根本没有实例。类组件有实例,但是你不必自己创建一个组件实例 —— React 自己处理。

最终,使用 React.createElement()JSX, 或者 element factory helper 创建元素。不必通过在代码中写元素的简单类 —— 只要知到它们是隐藏的类就可以了。

深入阅读

1.所有的 React 元素由于安全原因都需要一个额外的 $$typeof: Symbol.for('react.element')字段声明。在上面的例子中遗漏了这段。这个博客的入口为元素使用内联对象,是为了让你知道下面会发生什么,但是代码不能运行,除非对元素你添加了 $$typeof,或者修改代码去使用 React.createElement() 或 JSX。

本文转自:开源中国社区 [http://www.oschina.net]
本文标题:React 组件,元素和实例
本文地址:
http://www.oschina.net/translate/react-components-elements-and-instances
参与翻译:
祝青, leoxu, gracesyuan

英文原文:React Components, Elements, and Instances


时间:2016-01-16 11:28 来源:开源中国社区 作者:oschina 原文链接

好文,顶一下
(0)
0%
文章真差,踩一下
(0)
0%
------分隔线----------------------------


把开源带在你的身边-精美linux小纪念品
无觅相关文章插件,快速提升流量