组件,实例和元素之间的不同点困扰了很多 React 初学者。为什么会有三个不同的术语指向屏幕的绘图。 管理实例如果你是 React 新手,也许你以前只使用过组件类和实例。例如你也许会通过创建一个类的方式声明一个 Button 组件。当 App 运行的时候,你也许会在屏幕上创建几个 Button 组件的引用,每个引用都有自己的属性和本地状态。这是传统的面向对象 UI 程序。那为什么会有元素呢? 在这个传统的 UI 模块里,由你来管理子组件实例的创建和销毁。如果一个 Form 组件想渲染一个 Button 组件,它需要创建它的实例,并手动的保持信息的更新。
这虽然是伪代码,但是通过一个面向对象的方式使用像 BackBone 这样库时,你最终还是或多或少的像通常那样写一个复合 UI 代码。 每一个组件都得保留到其DOM节点还有其子组件实例的引用,并且在正确时间对它们进行创建、更新和销毁操作。代码行数量的增长是潜在的组件语句数量的平方,而父组件得直接访问它们的子组件实例,使得将来要对它们进行解耦会很困难。 React 如何不同了呢? 元素来描述树在React中,由元素来解决问题。元素就是一个单纯的对象,它描述了一个组件实例或者DOM节点机器需要的属性。 它只包含有关于组件类型(例如:一个Button),组件的属性(例如:它的颜色),以及它里面的子元素这些信息。 元素并不是一个实在的实例,而是一种告诉React你想要在屏幕上看到什么东西的方式。你不能在元素上面调用方法。它只是一个不可修改的描述性对象,带有两个属性域:type:string|ReactClass)和props:Object1 。 DOM 元素当元素的类型为string时,就用它来代表以元素type值作为标记名称、以props值作为其属性的DOM节点。这就是React将会渲染的东西。例如:
这个元素只是一种将下面的HTML表示为一个纯对象的方式:
注意元素是如何被嵌套的。根据约定,当我们想要常见一个元素树时,我们会将一个或者更多个子元素指定为它们容器元素的children属性。 重要的是子元素和父元素都只是描述性的,而不是实实在在的实例。当你创建它们时,它们并不代表屏幕上的任何东西。你可以将它们创建出来然后仍在一旁,并没有什么关系。 React 元素容易移动,不需要做什么转换,当然也不实际的DOM元素更轻量——它们都只是对象而已啦! 组件元素不过,元素的类型也可以是一个对应到React组件的函数或者类:
这是 React 的核心概念。 描述一个组件的元素还是一个元素,就跟一个描述DOM节点的元素没什么两样。它们可以相互嵌套和混合: 这个特性然你可以用一个带有特殊的 color 属性的Button来定义一个DangerButton,不用担心Button会被渲染成一个DOM(button)、一个<div>还是别的什么东西:
你可以在一个单独的元素中混合并匹配DOM和组件元素:
或者如果你选择了JSX的话 JSX:
这样的混合和匹配有助于组件同其它东西的解耦,因为他们可以通过组合同时表达出is-a和has-a的关系:
组件封装了元素树当 React 发现了一个带有函数或者类类型的元素,它就会知道要根据提供的对应 props 请求向那个组件请求其所渲染的元素。 当它发现这个元素时:
React 会向 Button 请求其所渲染的东西。Button 将会返回这个元素:
React 会重复这一过程,知道它已经知悉了页面上每个组件所依赖的 DOM 标记元素。 React 就像是一个小孩,会针对每个“X 是 Y”的命题提出“什么是 Y”这种问题,要你来做出解释,直到他们搞明白了事情的所有细节。 还记得上面的 From 示例吗?它可以用 React 写成下面这个样子1:
这就是了! 对于一个 React 组件而言,props 是输入,而一个元素树就是输出。 所返回的元素树可以同时包含描述了 DOM 节点的元素,以及描述了其它组件的元素。这样就让你能将 UI 的各个独立部分组装起来,而不用去依赖于它们的内部 DOM 结构。 我们让 React 来创建、更新和销毁实例,用元素来描述它们并将它们从组件那里返回,而 React 会负责管理这些实例。 组件可以是类或者函数在上述的代码中, Form, Message, 和 Button 都是 React 组件。他们可以想上面那样写成函数, 也可以是继承自 React.component 的类。这里这三种声明组件的方式几乎是对等的:
当把组件定义成类是,会比一个函数式的组件更强大一些。它可以在对应的 DOM 节点创建或者销毁时存储一些本地状态并执行一些自定义的逻辑。 函数式组件功能较少不过更简单,使用一个 render() 方法的效果同类组件相似。除非你需要使用一种只在类中具备的功能特性,我们建议你还是使用函数式的组件。 不过,函数亦或者类,基本上他们都是 React 的组件,所以都是以 props 作为输入,以返回的元素作为输出。 自上而下的整合当你像下面这样调用:
React 会根据这些 props 向 From 组件请求其返回的元素树。最终它会以一种相对更简单的原语来“提炼”其对你的组件树的理解:
这就是 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。 |