本篇为译文,原文出处:React Elements vs React Components vs Component Backing Instances
许多人可能听说过 Facebook 的 React 库,并在自己的工作或项目中使用它。React 是非常流行的,使开发用户界面变得简单且符合声明式编程(译者注:声明式编程可以参考声明式编程和命令式编程的比较)。
正如题目所示,React 有几个概念遍及其文档,这(几个概念)可能会让新React用户困惑。例如,你在 Glossary、Top-Level API 和 Explanation on Refs 这几章中检索 “React Element”、“React Component” 和 “Component Backing Instance”,你会发现这几个术语遍及各处,已经是 React 的绝对的核心。
这篇文章不是一篇 React 的教程,更多的是分享我最近学习 React 而得到的一些(知识)。因此,这篇文章是针对之前已经涉猎过 React 的人们,我会假设你已经熟悉 React 一些核心概念和语法。
React元素是什么?
React 元素这个概念对于 React 来说很重要,但是我坚信一点,因为使用 React 和 JSX 来构建用户界面过于简单,从而导致可能在最初错过这个概念。如果你之前用过 React + JSX,毫无疑问的说,在你的 JS 文件中书写类 HTML 语法更加符合你的习惯(译者注:Habit is second nature. 习惯成自然)。在这种(书写类HTML语法)假象之下,JSX 语法实际上被编译成特殊的函数调用 — React.createElement()
。 猜猜 React.createElement()
会产生什么?耶,你猜对了,就是 React 元素。让我们看一个转换过程的例子:
// 使用 JSX 语法
var helloWorld = <div>Hello World!</div>;
// 然后是 JSX 被编译成 JavaScript 后的结果
var helloWorld = React.createElement(
"div",
null,
"Hello World!"
);
// 再是处理成 JavaScript 简单对象后看起来类似这样
var helloWorld = {
key: null,
props: {
children: "Hello World!" // more stuff in here
},
ref: null,
type: "div"
// more stuff in here
};
React 元素是由一些属性(properties)构成的 JavaScript 简单对象(plain old JavaScript objects,译者注:关于更多解释可以看 Plain Old JavaScript,What is a plainObject in JavaScript?):
- key - 属性 key 是用来在一组相同类型的 React 元素构成的 Array 中标识唯一特定 React 元素。你不必提供一个值给它(译者注:在新版 React 中,如果在循环一组相同类型的 React 元素时不指定 key,会报一个 warning 错误,具体可以参考 React 对循环 warning 提示增加 key 的研究),但是如果你给 key 提供一个值,React 将能够进行优化,使重新渲染过程更高效。
- props - 属性props 恰恰是你所认为的: 它是向下传递给子组件(child components)的所有 props 和 values 的映射(集合)。
- ref - 属性ref 用来访问与这个 React 元素相关联的经过(浏览器或渲染引擎)渲染处理后的底层 DOM 元素。
- type - 属性type 将是两种格式之一,表示任何有效的 HTML 元素的(名称)字符串 或 指向 React 组件类的引用。
此时, 了解每个属性所有细节并不重要。最大的收获在于,React 元素仅仅是 JavaScript 简单对象(plain old JavaScript objects),用来描述组件的 HTML 应是怎样的结构,这个对象上不包含任何方法(Methods),仅仅只有数据。
通过之前 helloWorld
React 元素的例子告诉你,helloWorld
表示一个子节点为“Hello World!”文本的 div 标签。即使你创建了一个更复杂的 JSX 例子,生成的 React 元素仍将是一个 JavaScript 对象,配合其他嵌套 React 元素描述 HTML 将是怎样的结构。如果这让你感觉熟悉,是因为这正是虚拟 DOM(Virtual DOM)是什么(的解释) - 它是 DOM 在特定时间段应该是怎样的结构的描述,通过 React 元素声明来表示。
React组件是什么?
不同于 React 元素,你可能对 React 组件更熟悉一些。如果你曾写过类似下面例子的代码,那么你之前已经创建过 React 组件类了:
// 这是一个 React 组件类
class CustomForm extends React.Component {
constructor(props) {
super(props);
this.state = {
inputText: "Willson"
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(e) {
const inputText = e.target.value;
this.setState({ inputText });
}
render() {
return (
<div className="custom-form">
<div className="custom-form-header">
<p>Hello, {this.state.inputText}</p>
</div>
<div className="custom-form-body">
<input type="text"
value={this.state.inputText}
onChange={this.handleInputChange} />
</div>
</div>
);
}
}
/*
* 我假设你有可用的 React 和 ReactDom,以及
* 一个 id 为 "root" 的 HTML 元素
* ReactDOM.render() 的返回值是组件实例
*/
var componentInstance = ReactDOM.render(<CustomForm />, document.getElementById("root"));
上面的例子是用 ES2015 新的 Class 语法写的,但它几乎相当于使用 React.createClass()
。你也许熟悉编写这些组件类,但重要的是(React文档中所述的)React 组件指代的是一个 React 组件类的实例。
假如你熟悉 JavaScript 的话,当你听到到“实例化一个类”且智商在线时,你可能会考虑使用new
操作符。然而,(在 React 中)从不需要你用 new
操作符创建 React 组件。相反的是,你将使用 ReactDOM.render()
把一个 React 元素渲染成一个特定的 DOM 元素,与此同时,ReactDOM.render()
将返回一个 React 组件实例。
从 ReactDOM.render()
返回的组件实例可以调用在 React 组件类中定义的方法。反过来想一下会让你明白,ReactDOM.render()
是 React 为你实例化一个组件的机制。通常, 你不需要访问组件实例本身, 但我发现在测试 React 组件时, 将组件实例的引用保存下来(对测试)很有帮助。
与 ReactDOM.render()
有关的最后一个有趣的点是,React 在 ReactDOM.render()
时对虚拟 DOM 执行高效的 diff 算法(译者注: diff 算法延伸阅读)。如果你记得(上面说的),React 元素表示虚拟 DOM(元素),这意味着 ReactDOM.render()
接收一个虚拟 DOM(元素),将其渲染成一个真实 DOM 元素,返回(React 元素 type 指定的)组件实例。在底层,如果你将相同的React元素类型(带有可能不同的 props 值)通过 ReactDOM.render()
渲染成相同的 DOM 元素,React 将执行 diff 算法,对 DOM 元素进行仅必须的最小更改。也许令人惊讶的是,它每次返回相同的组件实例 - 除了更新的 prop 和 state 值。
组件支撑实例是什么?
前两节探讨了 React 元素和 React 组件实例,这两个概念都是 DOM 之上的抽象层。 现在我们将讨论术语“组件支撑实例”,以及它们自身如何与真实 DOM 元素相关联。
// 组件类
class CustomForm extends React.Component {
constructor(props) {
super(props);
this.state = {
inputText: "Willson"
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(e) {
const inputText = e.target.value;
this.setState({ inputText });
}
render() {
return (
<div className="custom-form">
<div className="custom-form-header">
<p>Hello, {this.state.inputText}</p>
</div>
<div className="custom-form-body">
<input type="text"
value={this.state.inputText}
onChange={this.handleInputChange} />
</div>
</div>
);
}
}
// 组件实例
var componentInstance = ReactDOM.render(<CustomForm />, document.getElementById("root"));
// DOM 实例
var domInstance = ReactDOM.findDOMNode(componentInstance);
// 原文作者这里出了个 Bug,多了一个 D
在前面的实例中,ReactDOM.render()
通过将 React 元素渲染到现有的 DOM 元素中来生成一个组件实例。正如我们已经知道的,组件实例不是真实 DOM 节点。然而,访问与组件实例关联的底层 DOM 节点实际上很简单 - 我们使用 ReactDOM.findDOMNode()
,并将组件实例作为其参数传递。那么这与“组件支撑实例”术语有什么关系呢?让我们一脸懵逼的是,React 在其文档的各处将引用的真实 DOM 节点称之为组件支撑实例。
总结
这3个术语“React 元素”,“React 组件”和“组件支撑实例”是密切相关的。 由于 JSX 语法被转译为 React.createElement()
调用(译者注:理解转译这个概念可以参考 Compiling Vs Transpiling),最终返回我们称之为“React 元素”的 JavaScript 简单对象(plain old JavaScript objects),你可以将 React元素视为基础构建单元。React 组件实例表示下一个抽象层 - ReactDOM.render()
接收一个 React 元素、引用一个真实 DOM 节点、返回一个 React 组件实例。该实例可以访问组件类中定义的方法,但是在单元测试之外很少用到这一点。React 元素和 React 组件实例都不表示真实 DOM 元素。渲染组件实例产生的 DOM 元素被称为组件支撑实例,访问它的主要方式是使用ReactDOM.findDOMNode()
。
我希望这篇文章能够帮助你理清这些术语!
[参考资料]
- ** Backing Instances** 翻译成 支撑实例 来自于 理解React中es6方法创建组件的this
- 声明式编程和命令式编程的比较
- React 对循环 warning 提示增加 key 的研究
- react精髓之一---diff算法
- Compiling Vs Transpiling