帮助
支持我们

虚拟 DOM

您可能听说过人们提到“虚拟 DOM”,并想知道:是什么让它“虚拟”?“虚拟”DOM 与我们在为浏览器编程时使用的真实 DOM 有什么不同?

虚拟 DOM 是使用对象对树形结构的简单描述

let vdom = {
  type: 'p',         // a <p> element
  props: {
    class: 'big',    // with class="big"
    children: [
      'Hello World!' // and the text "Hello World!"
    ]
  }
}

像 Preact 这样的库提供了一种构建这些描述的方法,然后可以将这些描述与浏览器的 DOM 树进行比较。随着树的每个部分进行比较,浏览器的 DOM 树会更新为与虚拟 DOM 树描述的结构相匹配。

这是一个有用的工具,因为它让我们能够以声明式而不是命令式的方式编写用户界面。我们不必描述如何更新 DOM 来响应键盘或鼠标输入等操作,而只需要描述在接收到该输入后 DOM 应该是什么样子。这意味着我们可以反复向 Preact 提供树结构的描述,而它会更新浏览器的 DOM 树以匹配每个新描述——无论其当前结构如何。

在本章中,我们将学习如何创建虚拟 DOM 树,以及如何告诉 Preact 更新 DOM 以匹配这些树。

创建虚拟 DOM 树

有几种方法可以创建虚拟 DOM 树

  • createElement():Preact 提供的一个函数
  • JSX:可以编译为 JavaScript 的类似 HTML 的语法
  • HTM:可以直接在 JavaScript 中编写的类似 HTML 的语法

从最简单的方法开始是有用的,即直接调用 Preact 的 createElement() 函数

import { createElement, render } from 'preact';

let vdom = createElement(
  'p',              // a <p> element
  { class: 'big' }, // with class="big"
  'Hello World!'    // and the text "Hello World!"
);

render(vdom, document.body);

上面的代码创建了一个段落元素的虚拟 DOM“描述”。createElement 的第一个参数是 HTML 元素名称。第二个参数是元素的“props”——一个包含要设置在元素上的属性(或特性)的对象。任何其他参数都是元素的子元素,它们可以是字符串(如 'Hello World!')或来自其他 createElement() 调用的虚拟 DOM 元素。

最后一行告诉 Preact 构建一个与我们的虚拟 DOM“描述”匹配的真实 DOM 树,并将该 DOM 树插入到网页的 <body> 中。

现在使用更多 JSX!

我们可以使用 JSX 重写前面的示例,而无需更改其功能。JSX 让我们能够使用类似 HTML 的语法来描述段落元素,这有助于我们在描述更复杂的树时保持可读性。JSX 的缺点是我们的代码不再用 JavaScript 编写,并且必须由诸如 Babel 之类的工具进行编译。编译器会将下面的 JSX 示例转换为我们在前一个示例中看到的精确的 createElement() 代码。

import { createElement, render } from 'preact';

let vdom = <p class="big">Hello World!</p>;

render(vdom, document.body);

现在看起来更像 HTML 了!

关于 JSX,还有一件事需要记住:JSX 元素内部的代码(在尖括号内)是特殊语法,而不是 JavaScript。要使用数字或变量之类的 JavaScript 语法,首先需要使用 {expression} 从 JSX 中“跳”出来 - 类似于模板中的字段。下面的示例显示了两个表达式:一个将 class 设置为随机字符串,另一个计算数字。

let maybeBig = Math.random() > .5 ? 'big' : 'small';

let vdom = <p class={maybeBig}>Hello {40 + 2}!</p>;
                 // ^---JS---^       ^--JS--^

如果我们要 render(vdom, document.body),则会显示文本“Hello 42!”。

再次使用 HTM

HTM 是 JSX 的替代品,它使用标准 JavaScript 标记模板,无需编译器。如果你没有遇到过标记模板,它们是一种特殊的字符串文字,可以包含 ${expression} 字段

let str = `Quantity: ${40 + 2} units`;  // "Quantity: 42 units"

HTM 使用 ${expression} 而不是 JSX 中的 {expression} 语法,这可以更清楚地表明代码的哪些部分是 HTM/JSX 元素,哪些部分是纯 JavaScript

import { html } from 'htm/preact';

let maybeBig = Math.random() > .5 ? 'big' : 'small';

let vdom = html`<p class=${maybeBig}>Hello ${40 + 2}!</p>`;
                        // ^--JS--^          ^-JS-^

所有这些示例都会产生相同的结果:一个虚拟 DOM 树,可以提供给 Preact 来创建或更新现有的 DOM 树。


绕道:组件

我们将在本教程的后面详细介绍组件,但现在重要的是要知道像 <p> 这样的 HTML 元素只是两种虚拟 DOM 元素类型之一。另一种类型是组件,它是一种虚拟 DOM 元素,其中类型是函数,而不是像 p 这样的字符串。

组件是虚拟 DOM 应用程序的构建模块。现在,我们将通过将 JSX 移入函数来创建一个非常简单的组件

import { createElement } from 'preact';

export default function App() {
    return (
        <p class="big">Hello World!</p>
    )
}

render(<App />, document.getElementById("app"));

在将组件传递给 render() 时,务必要让 Preact 进行实例化,而不是直接调用组件,否则会以意外的方式中断

const App = () => <div>foo</div>;

// DON'T: Invoking components directly breaks hooks and update ordering:
render(App(), rootElement); // ERROR
render(App, rootElement); // ERROR

// DO: Passing components using createElement() or JSX allows Preact to render correctly:
render(createElement(App), rootElement); // success
render(<App />, rootElement); // success

试一试!

在此页面的右侧,您将在顶部看到我们先前示例中的代码。其下方是一个框,其中包含运行该代码的结果。您可以编辑代码,并查看您的更改如何影响(或中断!)结果。

要测试您在本教程中学到的内容,请尝试为文本添加更多活力!通过将其包装在 HTML 标签中使单词 World 突出显示:<em></em>

然后,通过添加 style prop 使所有文本变为 紫色style prop 是特殊的,并允许使用一个或多个 CSS 属性的对象值设置在元素上。要将对象作为 prop 值传递,您需要使用 {expression},例如 style={{ property: 'value' }}

正在加载...