资讯专栏INFORMATION COLUMN

虚拟DOM内部是如何工作的

hiYoHoo / 2639人阅读

摘要:但是它与里大部分的概率是保持一致的。但是如何将转换成函数的调用呢就是干这件事情的。好了,让我们看看是如何工作的。下面的图片在流程图中高亮了一个组件是如何工作的最后希望这篇文章能帮助你理解是如何工作的至少在中

英文原文链接

Virtual DOM很神奇,同时也比较复杂,难以理解。react,preact和相似的js库都使用了virtual dom。然而,我找不到任何好的文章或者文档,可以详细地又容易理解的方式来解释它。因此我决定自己写一篇。

注意:文章篇幅较长,文中有大量的图片来帮助理解。文中使用的是preact的代码,因为它体积小,容易阅读。但是它与React里大部分的概率是保持一致的。希望阅读完这篇文章后,你可以更好地理解React和Preact这样的类库,甚至为它们作出贡献。

在这篇文章中,我将列举一个简单的例子来解释以下这些是如何工作的:

Babel和JSX

创建VNode-一个简单的virtual DOM元素

处理组件和子组件

初始化渲染和创建一个DOM元素

重新渲染

移除DOM元素

替换DOM元素

The app

这是一个简单地可筛选的搜索应用,它包含了两个组件FilteredListListList组件用来渲染一组items(默认:"California"和"New York")。这个应用有一个搜索框,可以根据字母来过滤列表项。非常地直观:

概览图

我们用jsx来写组件,它会被babel转换成纯js,然后Preact的h函数会将这段js转换成DOM树,最后Preact的Virtual DOM算法会将virtual DOM转换成真实的DOM树,来构建我们的应用。

在深入Virtual DOM的生命周期之前,我们先理解一下jsx,因为它为库提供了入口。

Babel And JSX

在React,Preact这样的类库中,没有HTML标签,取而代之的是,一切都是javascript。所以我们要在js中写HTML标签,但是在js中写HTML简直就是噩梦?

对于我们的应用来说,我们将会像下面这样来写HTML


这就是jsx的由来。jsx本质上就是允许我们在javascript中书写HTML!并且允许我们在HTML中通过使用花括号来使用js。
jsx帮助我们像下面这样写组件

jsx转换成js

jsx很酷,但它不是合法的js,并且最终我们需要的是真实的DOM。JSX只是帮助编写一个真实DOM的替代品,除此之外,它别无用处。所以我们需要一种方法将它转换成对应的JSON对象(也就是Virtual DOM),作为转化成真实DOM的输入。我们需要一个函数来实现这个功能。

在Preact中h函数就是干这件事情的,等同于React中的React.createElement

但是如何将jsx转换成h函数的调用呢?Babel就是干这件事情的。Babel遍历每个jsx节点,并将它们转换成h函数调用。

Babel JSX(React vs Preact)

默认情况下,Babel将jsx转换成React.createElement调用

但是我们可以很容易地将函数名修改成任何名称,只需要在babelrc中配置一下即可

Option 1:
//.babelrc
{   "plugins": [
      ["transform-react-jsx", { "pragma": "h" }]
     ]
}
Option 2:
//Add the below comment as the 1st line in every JSX file
/** @jsx h */

挂载到真实DOM

不仅仅是render中的代码会被转换成h函数,最初的挂载也会!

这就是代码执行开始的地方

//Mount to real DOM
render(, document.getElementById(‘app’));
//Converted to "h":
render(h(FilteredList), document.getElementById(‘app’));
h函数的输出

h函数将jsx转化后的内容转换成Virtual DOM节点。一个Preact的Virtual DOM节点就是一个简单的代表了单个包含属性和子节点的DOM节点的js对象,如下所示:

{
   "nodeName": "",
   "attributes": {},
   "children": []
}

比如,应用的input标签对应的Virtual DOM如下:

{
   "nodeName": "input",
   "attributes": {
    "type": "text",
    "placeholder": "Search",
    "onChange": ""
   },
   "children": []
}

注意:h函数并不是创建整棵树!它只是简单地创建某个节点的js对象。但是因为render方法。。。

好了,让我们看看Virtual DOM是如何工作的。

Preact中的Virtual DOM算法

在下面的流程图中,展示了在Preact中,组件是如何被创建、更新和删除的过程。同时也展示了像componentWillMount这样的生命周期事件是什么时候被调用的。

现在理解起来有些困难,所以我们一步一步来拆解流程图中的每种情况。

情景1:初始化app 1.1 创建Virtual DOM

高亮的部分展示了根据给定的组件生成的Virtual DOM树。注意一点这里并没有为子组件创建Virtual DOM

下面这幅图展示了应用首次加载时发生的情况。这个库最后为FilteredList组件创建了带有子节点和属性
的Virtual DOM

注意:在这个过程中还调用了componentWillMountrender生命周期方法(在上图中的绿色区块)

此时,我们有了一个Virtual DOM,div元素是父亲节点,带有一个input和一个list的子节点

1.2 如果不是一个组件,则创建真实的DOM

在这一步中,它只是为父亲节点创建一个真实DOM,对于子节点,重复这个过程

此时,我们在下图中只有一个div展示出来

1.3 对于子元素重复这个过程

在这一步中,循环所有的子节点。在我们的应用中,将会循环input和list

1.4 处理孩子节点和添加到父亲节点

在这一步中,我们将会处理叶子节点,由于input有个父节点div,那么我们将会将input添加到div中作为
子节点。然后流程转向创建List(第二个子节点是div)

此时,我们的app长下面这样

注意:在input被创建之后,由于它没有任何子节点,并不会立马就去循环和创建List组件。相反地,它会首先
input标签添加到父节点div中去,完事之后再返回处理List标签

1.5 处理子节点

现在控制流回到了步骤1.1,并且开始处理List组件。但是由于List是一个组件,所以它会遍历执行自身的render方法,从而获得一组VNodes,就像下面这样:

List组件的循环完成时,它会返回List的VNode,就像下面这样:

1.6 对于所有的子节点,重复步骤1.1到1.4

对于每个节点,它将会重复以上的每一步。一旦到达叶子节点,它将会被加入到父节点中去,并且重复这个过程。

下面的图片展示了每个节点是如何添加上去的(深度优先遍历)

1.7 处理完成

此时已经完成了处理过程。然后对于所有的组件,会调用componentDidMount方法(从子组件开始,直到父组件)

注意:当一切准备就绪,一个真实DOM的引用会被添加到每个组件的实例中。这个引用会在接下来的一些更新操作(创建、更新、删除)被用来比较,避免重复创建相同的DOM节点

情景2:删除叶子节点

当输入"cal"并按回车,这将会删除第二个列表子元素,也就是一个叶子节点(New York),同时其他父元素都会保留。

让我们看下这种情景下,流程是怎么样的

2.1 创建VNodes

在初始化渲染之后,后面的每次改变都是一次"更新"。当创建VNodes时,更新周期与创建周期非常相似,并且再一次创建所有的VNodes。不过既然是更新(不是创建)组件,将会调用每个组件和子组件相应的componentWillReceiveProps,shouldComponentUpdatecomponentWillUpdate方法。

另外,更新周期并不会重新创建已经存在的DOM元素。

2.2 使用真实DOM引用,避免创建重复的节点

之前提到过,在初始化加载期间,每个组件都有一个指向真实DOM树的引用。下面的图展示了引用是如何寻找我们的应用的。


当VNodes被创建后,每个VNode的属性都会与真实DOM的属性相比较。如果真实DOM存在,循环将会转移到下个节点

2.3 如果在真实DOM中有其它的节点,则删除

下面的图展示了真实DOM和VNode之间的不同


由于存在不同,真实DOM中的"New York"节点会被算法删除掉,正如下面图展示的那样。这个算法也称为"componentDidUpdate"生命周期。

情景3-卸载整个组件

举例:当输入blabla时,由于不匹配"California"和"New York",我们将不会渲染子组件List。这意味着,我们需要卸载整个组件


删除一个组件类似于删除一个多带带的节点。除此之外,当我们删除一个包含组件引用的节点,将会调用"componentWillUnmount",然后递归删除所有的DOM元素。在删除了所有的真实DOM元素之后,"componentDidUnmount"将会被调用。
下面的图片展示了真实DOM元素"ul"包含了指向"List"组件的引用。

下面的图片在流程图中高亮了deleting/unmounting一个组件是如何工作的

最后

希望这篇文章能帮助你理解Virtual DOM是如何工作的(至少在Preact中)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/87098.html

相关文章

  • JavaScript 如何工作:编写自己 Web 开发框架 + React 及其虚拟 DOM

    摘要:与大多数全局对象不同,没有构造函数。为什么要设计更加有用的返回值早期写法写法函数式操作早期写法写法可变参数形式的构造函数一般写法写法当然还有很多,大家可以自行到上查看什么是代理设计模式代理模式,为其他对象提供一种代理以控制对这个对象的访问。 这是专门探索 JavaScript 及其所构建的组件的系列文章的第 19 篇。 如果你错过了前面的章节,可以在这里找到它们: 想阅读更多优质文章请...

    余学文 评论0 收藏0
  • React

    摘要:基础创建虚拟参数元素名称,例如参数属性集合,例如,,,从参数开始,表示该元素的子元素,通常这些元素通过创建,文本文件可以直接插入嘻嘻哈哈这是渲染器,将元素渲染到页面中。 React简介 FeceBook开源的一套框架,专注于MVC的视图V模块。实质是对V视图的一种实现。 React框架的设计没有过分依赖于某个环境,它自建一套环境,就是virtual DOM(虚拟DOM)。 提供基础AP...

    hlcc 评论0 收藏0
  • React 深入系列1:React 中元素、组件、实例和节点

    摘要:中的元素组件实例和节点,是中关系密切的个概念,也是很容易让初学者迷惑的个概念。组件和元素关系密切,组件最核心的作用是返回元素。只有组件实例化后,每一个组件实例才有了自己的和,才持有对它的节点和子组件实例的引用。 React 深入系列,深入讲解了React中的重点概念、特性和模式等,旨在帮助大家加深对React的理解,以及在项目中更加灵活地使用React。 React 中的元素、组件、实...

    LeviDing 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<