Logo
Section 1 / 7

元素的渲染

深入了解 React 元素以及如何在浏览器中渲染它们。

导入

元素的渲染

深入了解 React 元素以及如何在浏览器中渲染它们。

React 元素

**React 元素**到底是什么?

React 元素是 React 应用中最基本的构建单元。它们描述了屏幕上将要渲染的内容。

Asking Questions
Asking Questions

尽管名字听起来与 DOM 元素相似,但两者有一个关键区别:React 元素只是普通的 JavaScript 对象,因此创建起来非常轻量且高效。调用 React.createElement() 创建 React 元素时,并不会触发真实的 DOM 操作。

注意:React 的 元素(elements)常被误认为是 React 的 组件(components),甚至混用这两个术语,这是不正确的。元素构成了 组件 的基本组成部分。


如何渲染一个 React 元素?

我们已经知道如何创建一个 React 元素。JSX 让我们可以避免冗长的 React.createElement() 调用,从而节省大量代码。但接下来的问题是:如何将一个元素渲染到屏幕上,也就是在浏览器中显示出来?

为此,我们需要使用 ReactDOM 提供的 render() 方法。此外,还需要一个 根节点(root node)或称为 挂载节点(mount node),作为渲染 React 元素的目标容器。该节点充当占位符,告诉 React 应该把元素插入到 DOM 的哪个位置。理论上,HTML 文档中可以存在多个不同的根节点,React 会独立管理这些节点并追踪各自的挂载状态。因此,你不必只构建一个大型 React 应用,也可以选择在一个 HTML 页面中运行多个小型(或大型)的独立应用。但在大多数情况下,我们的 React 应用只需要一个根节点即可。

现在来回答关键问题:如何渲染一个 React 元素?首先,我们将要渲染的元素作为第一个参数传给 ReactDOM.render(),然后将 根节点 作为第二个参数传入,即目标 DOM 节点,React 会将元素渲染进这个节点中。

假设我们想在 HTML 文档中渲染一个 ID 为 root 的 div,它将作为我们的 根节点:

1<!DOCTYPE html> 2<html> 3 <head> 4 <title>Hello!</title> 5 </head> 6 <body> 7 <div id="root"></div> 8 </body> 9</html>

对应的渲染调用如下所示:

1const myFirstElement = <div>My first React Element</div>; 2ReactDOM.render(myFirstElement, document.getElementById('root'));

如果你在浏览器中执行这段代码,你会在 root div 中看到以下内容:

1<div>My first React element</div>
...
div with the id 'root' rendered in the HTML document

No configuration found for ID: 2978ca0b683037fb2a8f2a5f8e660baa1ea26d820774615c883a85bf9706d7af

请动手尝试上面的代码片段,观察实际渲染结果!

注意:你也可以在 JSX 中渲染多个元素。只需将这些元素包裹在一对括号 () 内,并确保所有要渲染的元素都嵌套在一个外层元素中。
以下是一个示例:

1const myFirstElement = ( 2 <div> 3 <h2>第一个标题</h2> 4 <h3>第二个标题</h3> 5 <div>我的第一个 React 元素</div> 6 </div> 7);

协调过程(Reconciliation Process)

需要记住的是,React 元素是 不可变的(immutable),也就是说一旦创建就不能更改。元素创建后,仅代表用户界面某一时刻的状态。官方 React 文档将其比喻为电影中的单帧画面。如果我们想要更新应用的界面,就必须创建一个新的 React 元素,包含更新后的数据。不用担心,这听起来比实际复杂得多!

React 非常聪明,能够自动识别出应用中哪些部分发生了变化,并仅更新那些真正改变的部分。React 会将当前元素及其子元素与前一次的版本进行比较,仅当发现差异时才会触发真实的 DOM 操作。如果使用得当,React 的 协调过程 可显著提升应用性能,因为频繁操作原生 DOM 开销很大,严重影响性能。根据具体 React 元素的变化情况,你可能并不需要每次都重新渲染整个 DOM 节点,很多时候只需更新少数几个属性即可。

...
React reconciliation process

我们通过一个实际例子来看一下:

No configuration found for ID: 34d079512b9a4a1e2b79c5b0f4ccdf1ea9f47095370596647eeeacb6263d0174

再次创建了一个 React 元素。当它被 ReactDOM.render() 调用时,会显示当前时间。因为我们关注时间的实时性,所以把这个渲染调用封装成一个函数,并每 1000 毫秒执行一次。

通过 Chrome 开发者工具检查 DOM 元素可以发现:每次调用 ReactDOM.render() 时,只有时间文本本身被更新了。其余元素、DOM 节点以及未受影响的文本部分均保持不变:

...
React only updates the time, nothing else.

声明式编程

我们刚刚实践了 React 最核心的原则之一:声明式构建用户界面。我们不是命令式地告诉应用“每秒钟手动去修改一次时间”,而是声明式地定义:我们希望在某个位置始终显示当前时间,每次重新渲染时自动反映最新状态。

如果不使用 React,实现类似功能可能会像这样:

1function changeTime() { 2 const time = new Date().toLocaleTimeString(); 3 const target = document.getElementById('root'); 4 target.textContent = 'It is now ' + time + " o' clock"; 5} 6setInterval(changeTime, 1000);

声明式编程的主要优势在于,我们只需描述期望的最终状态,说明“应该呈现什么样子”,而无需一步步控制如何达到该状态。因此,对于复杂应用而言,声明式方法极大地提升了代码可读性和简洁性,使程序更易于维护且不易出错。

注意:在实际开发中,ReactDOM.render() 通常只调用一次,一般发生在页面加载时。我们这里重复调用是为了演示 ReactDOM 与 React 元素之间的协作机制。
真正的更新由 组件(而非元素)驱动——当组件的状态(state)发生变化或接收到新的属性(props)时,会触发重新渲染。

再来看一个例子以加深理解。假设我们有一个数组 [1, 2, 3, 4, 5],想要获取它的最后一个元素。
如果是命令式编程,我们会这样做:

  • 遍历这个数组:[1, 2, 3, 4, 5]
  • 获取索引为 list.length - 1 的元素
  • 返回该元素

而如果是声明式编程,我们则会说:

  • 我有一个数组:[1, 2, 3, 4, 5]
  • 我想要这个数组的最后一个元素
...
Difference between imperative and declarative code

JSX 简介

React 组件的两种类型