这道题的答案有点复杂。
❝首先要搞清楚 element 和 component 是不是一回事? ❞
从技术上来说,ReactDOM 不会在 DOM 中渲染 React 组件或 React 元素。它渲染由其组件实例支持的 DOM 元素。对于类组件来说这是正确的。但是对于函数组件,ReactDOM 仅渲染 DOM 元素。函数组件没有实例(可以通过 this
访问),因此在使用函数组件时,ReactDOM 会渲染由函数返回的元素所生成的 DOM 元素。
你需要在这里理解的是,React 元素不同于 DOM 元素。React 元素只是 HTML 元素、React 组件或它们的混合的“描述”。
好吧,一个更好的面试题可能应该这样问:当你在JSX中使用 <MyComponent/>
之类的东西时,它是组件、元素还是实例?
这是一个元素,但不是 DOM 元素,而是一个 React元 素。因为任何 JSX 标签都会被转换为 React.createElement
再去调用。
但是要想让 React 继续使用这个 React 元素的话,必须调用一个函数或从一个类中创建实例。
你可能会在一些 React 教程中看到 组件(component)、元素(element) 和 实例(instance) 这些词。我在这里混用这些词是不对的,但是我认为 React 的初学者需要了解它们的区别。React 官方博客上有一篇文章专门说明这些概念,但我认为这些内容对于初学者来说还远远不够。
以下是这些术语的简单定义:
this
关键字。你不需要手动从类创建实例,只需要记住它就在 React 的内存中即可。最重要的是,ReactDOM 不会在浏览器中渲染组件,也不会渲染元素(这里的术语元素代表 React.createElement
的返回值)。它也不渲染实例。它只渲染 DOM 元素。
不幸的是,使用术语组件既指模板又指通过模板使用的任何一种实例或者调用,这似乎是很普遍的。人们对此感到困惑很正常,这挺痛苦的。
每个 React 应用都从一个使用 React element 的 render
调用开始。下面以 reactjs.org 官网提供的 HelloMessage
案例作为例子,我对这个例子稍微做了一些修改,使其具有了函数组件:
const Today = () => (
<div>Today is {new Date().toDateString()}</div>
);class HelloMessage extends React.Component {
render() {
return (
<React.Fragment>
<div>Hello {this.props.name}</div>
<Today />
</React.Fragment>
);
}
}ReactDOM.render(
<HelloMessage name="Taylor" />,
mountNode
);
第一个 React 元素是我们在 ReactDOM.render
调用中开始的元素:
<HelloMessage name="Taylor" /> // 这是 React element
这个 React 元素描述了要渲染的 DOM 树应该以 HelloMessage
组件和值等于 Taylor
的 prop name
开始。
HelloMessage
是什么?每当 React 元素描述一个 React 组件时(就像上面的 React 元素一样),React 使用该组件将描述替换为组件返回的内容。这时它将会为基于类的组件创建一个实例,并将该实例的引用保留在内存中。它不会为基于函数的组件创建任何内容。它只是调用它们。
从 HelloMessage
组件返回的是一个描述 React.Fragment
组件的 React 元素。
React.Fragment
是什么?React 会持续不断的减少这些组件的未知描述,直到只存在有效的 DOM 节点。React.Fragment
的描述被翻译成 2 个React 元素,一个描述 div
,另一个描述 Today
组件。
Today
什么是?它调用 Today
函数来找出最后一个问题。Today
函数返回描述一个 div
的 React 元素。
至此,virtual 树中包含了所有描述 DOM 节点的 React 元素。React 通过一致性比较算法找出要在浏览器中更新的内容。用组件实例所转换的树节点保留修改该实例的能力。