zoukankan      html  css  js  c++  java
  • [翻译]Review——The Inner Workings Of Virtual DOM

    The Inner Workings Of Virtual DOM 虚拟DOM的内部工作机制

    原文地址:https://medium.com/@rajaraodv/the-inner-workings-of-virtual-dom-666ee7ad47cf

    虚拟DOM(Virtual DOM,简称VDOM aka VNode)是一种魔法。React、Preact和同类JS库在内核中都使用它。

    这篇文章讲述它是如何工作的,大体内容如下:

    1. Babel and JSX
    2. Creating VNode — A single virtual DOM element
    3. Dealing with components and sub-components
    4. Initial rendering and creating a DOM element
    5. Re-rendering
    6. Removing DOM element.
    7. Replacing a DOM element.

    零、前言

    示例app是一个可过滤的搜索app,包含“过滤清单”和“列表”两部分。

    Live app: http://codepen.io/rajaraodv/pen/BQxmjj

    大图景:

    在高层面上,我们用JSX(html in JS)来写组件,然后它们将会被CLI工具Babel转换为纯JS,然后Preact的超文本功能会将纯JS转为VDOM,并最终经由Preact的虚拟DOM算法,从VDOM中创造出真正的DOM,进而创造出我们的app。

    一、Babel And JSX

    Preact的一段语句长这样:

    JSX允许我们在JS里写html,同时允许我们在html里用{ }写JS。最终JSX把我们的组件变成这样:

    JSX虽然很酷,但并不是有效的JS,因为我们需要真实的DOM。所以我们需要转换成相应的JSON对象(VDOM,也是一棵树),这样我们最终可以将它用作创建真实DOM的输入。我们需要一个功能来做到这一点。这个功能在Preact里就是 “h” function,在React就是“React.createElement”。

    但是如何将JSX转换为“h”函数调用?这就是Babel出场的地方,它将每一个经历的JSX节点转换成“h”函数调用。

    默认情况下,Babel将JSX转换为React.createElement调用,因为它默认为React。

    但是我们可以通过添加“Babel Pragma”轻松地将函数的名称更改为我们想要的任何内容(如Preact的“h”),如下所示:

    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 */

     “h”函数的输出如下:

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

    二、虚拟DOM的算法

    1.初始化app

    1.1 对给定组件创建虚拟DOM

    高两部分展示了初始循环时对给定组件创造虚拟DOM的过程。请注意,它并没有给子组件创造虚拟DOM,那是另一个循环才做的事。

    下图展示了App初次加载时发生了什么:

    请注意,我们同样称此为“componentWillMount” 和 “render”的生命周期方法(即上图绿色方框)。

    此时,我们的虚拟节点里有一个div父节点,它包含“input”和“list”子节点。

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

    这一步,它仅仅对父节点(div)创建了一个真实节点,并对其子节点重复这一过程。

    此时,我们的div如下图所示:

    1.3 对所有子节点进行重复操作

    此时,它对所有子节点(“input”和“list”项)进行循环。

    1.4 处理子节点并添加至父节点中

    在这一步中,我们将处理叶子。由于“input”具有父(“div”),因此我们将输入作为子项附加到div。然后控件停止并返回以创建“List”(这是“div”的第二个子节点)。

    此时,我们的app如下图所示:

    1.5 处理子组件

    控制返回到步骤1.1并重新开始“List”组件。但由于“List”是一个组件,它调用“List”组件的render方法来获取如下所示的新VNode集。

     该循环为List组件完成,并返回List的VNode,如下所示:

    1.6 对所有的子组件重复1.1-1.4步

    它将为每个节点再次重复上述步骤。到达叶节点后,它会将其附加到节点的父节点并重复该过程。

    下图展示了每个节点是如何被加上的(深度优先):

    1..7 处理完成

    此时,它完成了处理。它只是为所有组件调用“componentDidMount”(从子组件到父组件)并停止。

    场景二:删除叶节点

    假设我们键入“cal”并按Enter键。这将删除第二个列表节点,叶节点(纽约),同时保留所有其他父节点。

    2.1 像之前那样创建虚拟节点

    在初始渲染之后,未来的每个变化都是“更新”。在创建VNode时,更新周期与创建周期非常相似,并且会再次创建VNode。

    但由于它是组件的更新(而不是创建),它使“componentWillReceiveProps”,“shouldComponentUpdate”和“componentWillUpdate”调用每个组件和子组件。

    此外,更新周期,如果那些元素已经存在,则不会重新创建DOM元素。

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

    如前所述,每个组件都引用了在初始加载期间创建的相应真实DOM树。下图显示了此时引用如何查找我们的应用程序。

    当创建VNode时,每个VNode的属性与该节点上REAL DOM的属性进行比较。如果存在真实DOM,则循环移动到下一个节点。

    2.3如果REAL DOM中有额外的节点,则删除节点

    下图显示了REAL DOM VS VNode的差异。

    由于存在差异,REAL DOM中的“纽约”节点将被算法删除,如下面的工作流程所示。一切都完成后,该算法还会调用“componentDidUpdate”生命周期事件。

    场景3:卸载整个组件

    使用案例:假设我们在过滤器中输入了blabla,因为它与“California”或“New York”不匹配,我们根本不会渲染子组件“List”。这意味着,我们需要卸载整个组件。

    删除组件类似于删除单个节点。除此之外,当我们删除一个对组件有引用的节点时,框架会调用“componentWillUnmount”,然后递归删除所有DOM元素。在从真实DOM中删除所有元素之后,它调用引用组件的“componentDidUnmount”方法。

    下图显示了真实DOM“ul”上对“List”组件的引用。

    下图突出显示了流程图中显示删除/卸载组件的工作方式的部分:

    —— 完 ——

  • 相关阅读:
    【css】怎么让Chrome支持小于12px 的文字
    java操作linux,调用shell命令
    20个非常有用的Java程序片段
    Java集合详解
    SVN使用指南
    利用SQL语句查询数据库中所有表
    HttpClient-03Http状态管理
    HttpClient-02连接管理
    HttpClient-01基本概念
    IDEA安装插件
  • 原文地址:https://www.cnblogs.com/bbcfive/p/10625370.html
Copyright © 2011-2022 走看看