比较与理解 React 的 Components、Elements 和 Instances
React 是目前(2017.04)流行的创建组件化UI的框架,自身有一套完整和强大的生态系统;同时它也是我目前工作中的主力框架,所以学习和理解React是很自然的需求。
<div> <img href="https://www.wenjiangs.com/wp-content/uploads/2022/docimg15/320-1kgnb42oecz.png" className="profile" /> <h3>{[user.firstName, user.lastName].join(' ')}</h3> </div>;
借助 babel-plugin-transform-react-jsx,上面的JSX将被转译成:
var profile = React.createElement("div", null, React.createElement("img", { src: "avatar.png", className: "profile" }), React.createElement("h3", null, [user.firstName, user.lastName].join(" ")) );
那么,React.createElement
是在做什么?看下相关部分代码:
var ReactElement = function(type, key, ref, self, source, owner, props) { var element = { // This tag allow us to uniquely identify this as a React Element $typeof: REACT_ELEMENT_TYPE, // Built-in properties that belong on the element type: type, key: key, ref: ref, props: props, // Record the component responsible for creating this element. _owner: owner, }; // ... return element; }; ReactElement.createElement = function(type, config, children) { // ... return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props ); };
看起来,ReactElement.createElement
最终返回了一个对象,这个对象大概是这样的形状:
{ type, key, props: { children } }
非常明显,这就是一个 Elements Tree!很好,我们知道了 react 的 render 方法是返回一个 Elements Tree,react 的核心就是围绕 Elements Tree 做文章。
下面我们就主要讲讲 Components,Elements(Tree)和 Instances,以及三者之间的关系。
传统面向对象 UI 编程的痛点:管理实例
如果你是 React 的新手,那么之前你可能只接触过组件的类和实例(component classes and instances )。比如,你可能会
创建一个类来声明 Button
组件,当 app 运行时,屏幕上可能会有多个 Button
的实例,每个都有自己的属性和私有状态。这就是传统面向对象的 UI 编程,那么为什么要引入 Elements 的概念?
传统UI模型中,你必须自己负责创建和销毁子组件的实例(child component instances):
每个组件实例必须保存自己的 DOM nodes 和子组件实例的引用,并在对的时间创建,更新,销毁它们。代码的行数将会以可能的状态的数量的 平方 增长,而且组件可以直接访问子组件实例将会使解耦变得困难。
那么,React 有什么不同呢?
React 用 Elements Tree 描述 UI
An element is a plain object describing a component instance or DOM node and its desired properties.
一个元素(element)就是一个纯对象,描述了一个组件实例或 DOM node,以及它需要的属性。它仅仅包含这些信息:组件类型,属性(properties),及子元素。
元素不是实例,实际上,它更像是告诉React你需要在屏幕上显示什么的一种方式。它就是一个有2个数据域(field)的不可变描述对象(immutable description object):
{ type: (string | ReactClass), props: Object }
DOM Elements
当元素的 type 是 string 时,那么这个元素就表示一个 DOM node(type 的值就是 tagName,props 就是 attributes)。 这 node 就是 React 将渲染的。比如:
{ type: 'button', props: { className: 'button button-blue', children: { type: 'b', props: { children: 'OK!' } } } }
将被渲染成:
<button class='button button-blue'> <b> OK! </b> </button>
注意下元素是怎么嵌套的。当我们想创建元素树时,我们设置 children 属性。
注意:子元素和父元素都只是描述,并不是实际的实例。当你创建它们的时候,它们并不指向屏幕上的任何东西。 显然,它们比DOM轻量多了,它们只是对象。
Component Elements
此外,元素的 type 也可以是 function
或者 class
(即对应的 React Component):
{ type: Button, props: { color: 'blue', children: 'OK!' } }
An element describing a component is also an element, just like an element describing the DOM node. They can be nested and mixed with each other.
这是 React 的核心 idea:一个描述组件的元素同样是元素,和描述 DOM node 的元素没什么区别。它们可以互相嵌套和混合。
你可以混合搭配 DOM 和 Component Elements:
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:
const DeleteAccount = () => ( <div> <p>Are you sure?</p> <DangerButton>Yep</DangerButton> <Button color='blue'>Cancel</Button> </div> );
这种混合搭配帮助组件可以彼此解耦,因为它们可以仅仅通过组合(composition)就能表达 is-a
和 has-a
的关系:
Button
是有特定属性(specific properties)的DOM<button>
。DangerButton
是有特定属性的Button
。DeleteAccount
在<div>
里包含了Button
和DangerButton
。
Components Encapsulate Element Trees
当 React 碰到 type
是 function|class
时,它就知道这是个组件了,它会问这个组件:给你适当的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 nodes。对 React 组件来说,props 是输入,元素树(Elements tree)是输出。
我们选择让 React 来 创建,更新,销毁 实例,我们用元素来描述它们,而 React 负责管理这些实例。
Components Can Be Classes or Functions
声明组件的 3 种方式:
class
,推荐。React.createClass
,不推荐。function
,类似只有render
的class
。
Top-Down Reconciliation
ReactDOM.render({ type: Form, props: { isSubmitted: false, buttonText: 'OK!' } }, document.getElementById('root')); 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' } }; };
当你调用 ReactDOM.render
时,React 会问 Form
组件,给定这些 props,它要返回什么元素。React 会以更简单的基础值逐渐提炼(refine)它对 Form
组件的理解,这个过程如下所示:
// 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 叫做 reconciliation 的过程的一部分。每当你调用 ReactDOM.render()
或 setState()
时,都会开始 reconciliation 过程。在 reconciliation 结束时,React 知道了结果的 DOM 树,一个如 react-dom
或 react-native
的 renderer 会应用必须的最小变化来更新 DOM nodes(或平台特定的视图,如 React Native)。
这种渐进式的提炼(refining)过程也是 React 应用可以容易优化的原因。如果组件树的某部分大太了,你可以让 React 跳过这部分的 refining,如果相关 props 没有变化。如果 props 是 immutable 的话,非常容易比较它们是否变化, 所以 React 可以和 immutability 搭配一起并提高效率。
你可能注意到这篇文章讲了很多关于组件和元素,却没讲实例。这是因为相比传统面向对象的UI框架,在React中实例没那么重要。
仅仅以类声明的组件才有实例,并且你从来不会直接创建它——React 为你创建它。尽管有父组件实例访问子组件实例的机制,但这只是在必要的情况下才使用,通常应该避免。
总结
元素(Element)是 React 的一个核心概念。一般情况下,我们用 React.createElement|JSX
来创建元素,但不要以对象来手写元素,只要知道元素本质上是对象即可。
本文围绕 Components,Elements 和 Instances 来讲解了元素,而下一篇文章将借助 snabbdom 来讲 virtual-dom :怎么从元素生成对应的 dom,怎么diff元素来最小更新 dom。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
@zhongjie-chen
写的非常好,问几个疑惑的地方
1、虚拟dom对比是组件变成元素后再对比的吗
2、如果组件写的比较多,是不是增加了reconciliation的时间
3、是不是可以理解组件写入jsx中就是元素了
https://medium.com/@dan_abramov/react-components-elements-and-instances-90800811f8ca
这篇也挺棒的.
羡慕你们能玩commonjs的,可怜的amd只能用这种玩法