React快速入门:理解React虚拟DOM元素

众所周知,Web 网页由 HTML 标签组成,而 HTML 标签支持嵌套。换句话讲,一个标签内部可以包含其他标签,共同组成更复杂的 DOM 结构。以本站导航菜单为例,它是一个 ul 无序列表:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<ul>
  <li>
    <a href="https://fasionchan.com/algorithm/">小菜学算法</a>
  </li>
  <li>
    <a href="https://fasionchan.com/network/">小菜学网络</a>
  </li>
  <li>
    <a href="https://fasionchan.com/react/">小菜学React</a>
  </li>
</ul>

菜单是一个无序列表,由 ul 标签组成;每个列表项是一个 li 标签,嵌套在 ul 标签里;而菜单是一个链接,由 a 标签组成,嵌套在 li 标签里。

这些标签由浏览器渲染出来后,效果大致如下:

See the Pen for-var by fasionchan (@fasionchan) on CodePen.

子元素

那么,如何用 React 渲染出带嵌套的 HTML 标签结构呢?

上一小节,为了渲染出 h1 标签,我们调用 React 提供的接口创建了一个 React 元素。因此,可以将 React 元素当成 React 自己维护的 DOM 节点,用来描述实际 DOM 结构应该长什么样。

React 元素是一种虚拟的 DOM 节点,经过渲染后便得到浏览器能读懂的真实 DOM 节点。换句话讲,React 元素是真实 DOM 结构的另一种表现形式,跟真实的 DOM 节点一一对应。

浏览器 DOM 是支持嵌套的,React 元素也必然支持。那么,如何创建带嵌套关系的 React 元素呢?

问题答案得到 React 元素创建接口 React.createElement 中找,接口接收三个参数:

  • 第一个参数指定一个原生 HTML 标签或一个 React 组件,组件在本文后半部分介绍;
  • 第二个参数指定传给标签或组件的参数;
  • 其余参数则作为该元素的子元素;

举个例子,这个 React 元素表示 h1 标签,设置了 id 属性,包含一段文本 fasionchan.com

1
React.createElement("h1", {id: "title"}, "fasionchan.com");

这个 React 元素渲染出来后,得到的实际 DOM 结构大概是这样的:

1
<h1 id="title">fasionchan.com</h1>

如果将创建的 React 元素打印出来,可以看到它大概是这样的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
    $$typeof: Symbol(React.element),
    "type": "h1",
    "key": null,
    "ref": null,
    "props": {
        "id": "title",
        "children": "fasionchan.com"
    },
    "_owner": null,
    "_store": {}
}

其中,type 字段告诉 React 创建什么 HTMLSVG 标签,例子中是 h1props 字段代表创建 DOM 节点所需的参数和子节点( children )。由此可见,React 元素是一个普通的 JS 对象,负责告诉 React 如何构造实际的 DOM 节点。

React 元素中还有其他属性,例如 keyref 就是其中非常重要的两个,后续我们会经常用到。而 _owner_store$$typeof 这些则是 React 内部使用的,我们多半不用关心。

数据驱动

React 元素支持嵌套子元素,有能力构造很复杂的 DOM 结构,例如前面提到的导航菜单:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const menu = React.createElement(
  "ul",
  null,
  React.createElement(
    "li",
    null,
    React.createElement("a", {href: "https://fasionchan.com/algorithm/"}, "小菜学算法"),
  ),
  React.createElement(
    "li",
    null,
    React.createElement("a", {href: "https://fasionchan.com/network/"}, "小菜学网络"),
  ),
  React.createElement(
    "li",
    null,
    React.createElement("a", {href: "https://fasionchan.com/react/"}, "小菜学React"),
  ),
);

这段代码创建用来表示下拉菜单的 React 元素,最终渲染成 ul 标签;从第三个开始的参数,都是它的子元素,子元素最终渲染成 li 标签;每个子元素还包含一个代表 a 链接的子元素;而链接元素的子元素则是普通文本,表示菜单名。

我们调用 Reactmenu 这个元素渲染出来,效果大概如下:

1
2
3
const container = document.querySelector('#root');
const root = ReactDOM.createRoot(container);
root.render(menu);

See the Pen for-var by fasionchan (@fasionchan) on CodePen.

您可能会说,用 React 元素来描述 DOM 结构,比直接用 HTML 标签繁琐多了!何必多此一举呢?

实际上,网页 DOM 结构通常是根据应用数据来生成的。由于 React 元素是普通 JS 对象,因此我们可以编写 JS 代码根据数据自动创建 React 元素,这比直接写 HTML 标签方便多了。

下面通过一个简单的例子,来见证 React 元素在 JS 加持下的强大能力:

See the Pen for-var by fasionchan (@fasionchan) on CodePen.

菜单项保存在 items 数组里,每一项是一个对象,name 字段表示菜单名,url 表示菜单地址。我们调用数组 map 方法,为每个菜单项创建 React 元素,得到的数组作为子元素传给 ul 元素的构造函数。

由此可见,采用 JS 对象表示的 React 虚拟 DOM 元素,能够有效利用 JS 语言的强大表达力进行构建,这比静态的 HTML 标签灵活很多!

我们后续,还会介绍 JSX ,一种用于简化 React.createElement 调用的语法糖。JSX 语法跟 HTML 标签语法非常类似,像是支持 JSHTML 标签。届时,我们将看到 HTMLJS 两种特性结合后的强大表现力!

小菜学React】系列文章首发于公众号【小菜学编程】,敬请关注:

【小菜学React】系列文章首发于公众号【小菜学编程】,敬请关注: