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);
}
}
}
React Components, Elements, and Instances
组件、它们的实例和元素之间的区别使许多初学者感到困惑。为什么有三种不同的术语来指代在屏幕上绘制的东西?
管理实例
如果您刚接触React不久,您可能只与组件类、实例打过交道。例如,您可以通过创建一个类来声明一个按钮组件。当应用程序运行时,您可能在屏幕上有这个组件的几个实例,每个实例都有它自己的属性和本地状态。这是传统的面向对象的UI编程。为什么引入元素呢? 在这个传统的UI模型中,由您来负责创建和销毁子组件实例。如果表单组件想要呈现一个按钮组件,它需要创建它的实例,并手动保持它与任何新信息同步。
这是伪代码,但它或多或少是您编写复合UI代码时所得到的结果,该代码使用像Backbone这样的库以面向对象的方式运行。
每个组件实例都必须保留对其DOM节点和子组件实例的引用,并在时机合适时创建、更新和销毁它们。当遇到有state的组件,代码的行数可能成倍的增长,并且父组件可以直接访问他们的子组件实例,这使得将来很难将它们解耦。
那么,React有什么不同呢?
元素描述了树
在React中,这是元素来拯救的地方。元素是一个描述组件实例或DOM节点及其所需属性的普通对象。它只包含有关组件类型的信息(例如,一个按钮),它的属性(例如,它的颜色),以及其中的任何子元素。 元素不是实例。相反,它是一种告诉React你想在屏幕上看到什么的方法。在元素上不能调用任何方法。它只是一个具有两个字段的不可变描述对象:
type:(string |ReactClass)
和props:Object
。DOM Elements
当一个元素的类型是一个字符串时,它表示一个带有该标签名称的DOM节点,而
props
对应它的属性。这就是React所呈现的。例如:这个元素只是将以下HTML表示为普通对象的一种方法:
注意元素是如何嵌套的。按照约定,当我们想要创建一个元素树时,我们指定一个或多个子元素作为子元素的元素。 重要的是,子元素和父元素都只是描述,而不是实际的实例。当你创建它们时,它们不会指向屏幕上的任何东西。你可以创造它们,把它们扔掉,这无关紧要。 React元素很容易遍历,不需要解析,当然它们比实际的DOM元素要轻得多——它们只是对象!
Component Elements
但是,元素的类型也可以是
React component
对应的函数或类:这是React的核心思想。 描述组件的元素也是一个元素,就像描述DOM节点的元素一样。它们可以相互嵌套和混合。 这个特性让您可以将“危险按钮”组件定义为具有特定颜色属性值的按钮,而不必担心按钮是否呈现给DOM
< Button >、<div>
或其他内容:您可以在单个元素树中混合和匹配DOM和组件元素:
或者,如果您喜欢JSX:
这种混合和匹配有助于使组件彼此分离,因为它们可以通过组合来表达
isa
和hasa
关系。 按钮是一个带有特定属性的DOM < Button >。 “危险”按钮是一个具有特定属性的按钮。DeleteAccount
在<div>
中包含一个按钮和一个危险按钮。组件封装元素树
当React看到一个带有函数或类类型的元素时,它知道要问这个组件渲染什么元素,并给定相应的
props
。 当它看到这个元素:React会询问按钮渲染的元素内容。该按钮将返回此元素:
React将重复这个过程,直到它知道页面上每个组件的底层DOM标签元素。 React就像一个孩子问“Y”是什么意思,你向他们解释,直到他们明白了世界上的每一件小事。 还记得上面的表单示例吗?应用React它可以写成如下:
就是这样!对于React组件,props是输入,而元素树是输出。 返回的元素树可以包含描述DOM节点的元素和描述其他组件的元素。这使您可以在不依赖于其内部DOM结构的情况下组成UI的独立部分。 我们让React创建、更新和销毁实例。我们用从组件返回的元素来描述这些实例,React负责管理这些实例。
组件可以是类或函数。
在上面的代码中,
Form、Message、Button
都是React Component
。它们可以被编写为函数,比如上面的函数,或者作为从React.Component
继承的类。声明一个组件的这三种方法主要是等价的:当一个组件被定义为一个类时,它比功能组件更强大一些。当创建或销毁相应的DOM节点时,它可以存储一些本地state并执行自定义逻辑。 功能组件的功能不那么强大,但更简单,它的作用就像一个类组件,只有一个render()方法。除非您在类中只需要这个特性,否则我们鼓励您使用功能组件。 然而,无论是函数还是类,从根本上来说,它们都是React的组件。他们将这些props作为输入,并将元素作为输出返回。
Top-Down Reconciliation 自上而下的和解(解析)
当你调用:
考虑到这些
props
,React会问Form
组件它返回什么元素树。它将逐步“精炼”对您的组件树的理解,以更简单的原语:当您调用
ReactDOM.render()
或setState()
时,React会开始解析,这是React过程的一部分。在React解析结束时,它会知道要渲染DOM树,而一个类似于react-dom
或react-native
的渲染器会应用最小的更改集来更新DOM节点。 这个逐渐精炼的过程也是React应用程序易于优化的原因。如果组件树的某些部分变得太大,导致React不能有效地访问,那么您可以告诉React跳过这个“细化”,如果相关的props
没有改变的话,就把树的某些部分分散开来。如果它们是不可变的,那么就可以很快地计算出这些道具是否已经改变了,因此,React和 immutability的工作在一起很好,并且可以在最小的工作量下提供极大的优化。 您可能已经注意到,这个博客条目谈论了许多关于组件component和元素element的内容,而不是关于实例instance的很多内容。事实是,与大多数面向对象的UI框架相比,实例的作用要小得多。 只有被声明为类的组件才有实例,而您永远不会直接创建它们:对您React是这样的。当父组件实例访问子组件实例的机制存在时,它们只用于命令式操作(例如将焦点集中在字段上),并且通常应该避免。 React负责为每个类组件创建一个实例,这样您就可以以面向对象的方式编写组件,并使用方法和本地状态state,但除此之外,实例在React的编程模型中并不十分重要,并且是由React本身来管理的。总结
元素是一个简单的对象,描述您希望在屏幕上显示的DOM节点或其他组件。元素可以包含其他元素。创建一个React元素很便宜。一旦创建了一个元素,它就不会发生突变。 组件可以以几种不同的方式声明。它可以是带有render()方法的类。或者,在简单的情况下,它可以被定义为一个函数。在这两种情况下,它都以支持作为输入,并返回一个元素树作为输出。 当一个组件接收到一些props作为输入时,这是因为某个特定的父组件返回了带有它的类型和这些props的元素。 实例是您在编写的组件类中
this
的引用。它对于存储本地状态和响应生命周期事件非常有用。 功能组件根本没有实例。类组件有实例,但您永远不需要创建一个组件实例,React会负责这个。 最后,要创建元素,请使用React.createelement()、JSX
或element factory helper
。不要将元素作为一个对象写入真正的代码中,只需要知道它们都是底层的普通对象。