最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • React中的Virtual DOM

    正文概述 掘金(FruitBro)   2021-01-15   320

    前言

    Virtual DOM之于React,就好比一个虚拟空间,React的所有工作几乎都是基于Virtual DOM完成的。其中,Virtual DOM模型负责底层框架的构建工作,它拥有一整套的Virtual DOM 标签,并负责虚拟节点及其属性的构建、更新、删除等工作。那么Virtual DOM模型到底是如何构建虚拟节点,如何更新节点属性的呢?

    Virtual DOM的概念和诞生背景

    首先看DOM的概念,DOMDocument Object Model(文档对象模型)是表征页面元素的一个树形结构。 而Virtual DOM(即虚拟DOM),就是对真实DOM的一个抽象,是用JavaScript来描述的一个对象。 Virtual DOM是随着React的诞生而诞生的,由facebook公司提出。它的出现,主要是为了兼顾开发效率与性能。而继React之后,Vue 2.0版本也引入了Virtual DOM的概念。

    Virtual DOM的优势

    1. 性能的保障
    2. 提高开发效率
    3. 优秀的跨平台能力

    一、为什么我们不直接渲染DOM的更新呢?

    真实DOM

    React中的Virtual DOM 从上图我们可以看到一个DOM标签有非常多的属性,DOM操作是非常昂贵的,而看似复杂的Virtual DOM实际上效率更高。

    二、DOM标签所需的基本元素

    Virtual DOM模型中一个DOM标签所需的基本元素标签有哪些?其实,一套简易Virtual DOM 模型并不复杂,它只需要具备一个DOM标签所需的基本元素即可:

    • 标签名
    • 节点属性(包含样式、属性、事件等)
    • 子节点
    • 标识id

    示例代码如下:

    {
      // 标签名
      tagName: 'div',
      // 属性
      prototies: {
        // 样式
        style: {}
      },
      // 子节点
      children: [],
      // 唯一标识
      key: 1
    }
    
    {
      type: 'h1',
      props: {
        className: 'title',
        children: 'Hello World!'
      },
      key: null,
      ref: null
    }
    

    Virtual DOM 模型当然不止于此,却也离不开这些基础元素。现在就让我们揭下它的神秘面纱。

    Virtual DOM 中的节点称为 ReactNode,它分为3种类型 ReactElement(元素类型)ReactFragmentReactText(文本类型)。其中,ReactElement又分为ReactComponentElement(组件类型)ReactDOMElement(DOM类型)简单来说就是:元素类型(组件类型、DOM类型)、Fragment类型、文本类型

    下面是ReactNode中不同类型节点所需要的基础元素:

    type ReactNode = ReactElement | ReactFragment | ReactText;
    type ReactElement = ReactComponentElement | ReactDomElement;
    type ReactDOMElement = {
      type: stirng,
      props: {
        children: ReactNodeList,
        className: string,
        etc,
      },
      key: string | boolean | number | null,
      ref: string | null
    };
    type ReactComponentElement<TProps> = {
      type: ReactClass<TProps>,
      props: TProps,
      key: string | boolean | number | null,
      ref: string | null
    };
    type ReactFragment = Array<ReactNode | ReactEmpty>;
    type ReactNodeList = ReactNode | ReactEmpty;
    type ReactText = string | number;
    type REactEmpty = nul | undefined | boolean;
    

    那么,Virtual DOM 模型是如何根据这些节点类型来创建元素的呢?

    二、创建React元素

    下面是一段JSX与编译后的JavaScript

    const Nav, Profile;
    // 输入(JSX);
    const app = <Nav color='blue><Profile>click</Profile></Nav>;
    // 输出(JavaScript)
    const app = React.createElement(
      Nav,
      {color: 'blue'},
      React.createElement(Profile, null, "click")
    );
    

    在线babel编译器

    React中的Virtual DOM

    通过 JSX 创建的虚拟元素最终会被编译成调用 ReactcreateElement 方法。

    // createElement 只是做了简单的参数修正,返回一个 ReactElement 实例对象,
    // 也就是虚拟元素的实例
    ReactElement.createElement = function(type, config, children) { 
       // 初始化参数
       var propName; 
       var props = {}; 
       var key = null; 
       var ref = null; 
       var self = null; 
       var source = null; 
       
       // 如果存在 config,则提取里面的内容
       if (config != null) { 
         ref = config.ref === undefined ? null : config.ref; 
         key = config.key === undefined ? null : '' + config.key; 
         self = config.__self === undefined ? null : config.__self; 
         source = config.__source === undefined ? null : config.__source; 
        // 复制 config 里的内容到 props(如 id 和 className 等)
         for (propName in config) { 
           if (config.hasOwnProperty(propName) && 
           !RESERVED_PROPS.hasOwnProperty(propName)) { 
           props[propName] = config[propName]; 
           } 
         } 
       } 
       // 处理 children,全部挂载到 props 的 children 属性上。如果只有一个参数,直接赋值给 children,
       // 否则做合并处理
       var childrenLength = arguments.length - 2; 
       if (childrenLength === 1) { 
         props.children = children; 
       } else if (childrenLength > 1) { 
         var childArray = Array(childrenLength); 
         for (var i = 0; i < childrenLength; i++) { 
           childArray[i] = arguments[i + 2]; 
         } 
         props.children = childArray; 
       } 
      // 如果某个 prop 为空且存在默认的 prop,则将默认 prop 赋给当前的 prop
       if (type && type.defaultProps) { 
         var defaultProps = type.defaultProps; 
         for (propName in defaultProps) { 
           if (typeof props[propName] === 'undefined') { 
             props[propName] = defaultProps[propName]; 
           } 
         } 
       }
       // 返回一个 ReactElement 实例对象
       return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props); 
    };
    

    Virtual DOM 模型通过 createElement 创建虚拟元素,那又是如何创建组件的呢?

    三、初始化组件入口

    当使用React创建组件时,首先会调用instantiateReactComponent,这是初始化组件的入口函数,它通过判断node类型来区分不同组件的入口。

    • node为空时,说明node不存在,则初始化空组件ReactEmptyComponent.create(instantiateReactComponent)
    • node类型为对象时,即是DOM 标签组件或自定义组件,那么如果 element 类型为字

    符串时,则初始化DOM标签组件 ReactNativeComponent.createInternalComponent (element),否则初始化自定义组件 ReactCompositeComponentWrapper()

    • node 类型为字符串或数字时,则初始化文本组件 ReactNativeComponent.createInstanceForText(node)
    • 如果是其他情况,则不作处理。

    四、文本组件

    node类型为文本节点时是不算Virtual DOM元素的,但React为了保持渲染的一致性,将其封装为文本组件ReactDOMTextComponent

    在执行mountComponent方法时,ReactDOMTextComponent通过transation.useCreateElement判断该文本是否通过createElement方法创建的节点,如果是,则为该节点创建相应的标签和标 识 domID,这样每个文本节点也能与其他 React 节点一样拥有自己的唯一标识,同时也拥有了 Virtual DOM diff 的权利。但如果不是通过 createElement 创建的文本,React 将不再为其创建 <span>domID 标识,而是直接返回文本内容。

    五、DOM 标签组件

    Virtual DOM 模型涵盖了几乎所有的原生 DOM 标签,如 <div><p><span> 等。当开发者使用 React 时,此时的 <div> 并不是原生 <div> 标签,它其实是 React 生成的 Virtual DOM 对象,只不过标签名称相同罢了。React 的大部分工作都是在 Virtual DOM 中完成的,对于原生 DOM 而言,Virtual DOM 就如同一个隔离的沙盒,因此 React 的处理并不是直接操作和污染原生 DOM,这样不仅保持了性能上的高效和稳定,而且降低了直接操作原生 DOM 而导致错误的风险。

    ReactDOMComponent 针对 Virtual DOM 标签的处理主要分为以下两个部分:

    • 属性的更新,包括更新样式、更新属性、处理事件等;
    • 子节点的更新,包括更新内容、更新子节点,此部分涉及 diff 算法(diff算法请看参考文档React列表中的key属性)。
    1. 更新属性

    当执行 mountComponent 方法时,ReactDOMComponent 首先会生成标记和标签,通过 this.createOpenTagMarkupAndPutListeners(transaction) 来处理 DOM 节点的属性和事件。

    • 如果存在事件,则针对当前的节点添加事件代理,即调用 `enqueuePutListener(this,

    propKey, propValue, transaction)`。

    • 如果存在样式,首先会对样式进行合并操作 Object.assign({}, props.style),然后通过

    CSSPropertyOperations.createMarkupForStyles(propValue, this) 创建样式。

    • 通过 DOMPropertyOperations.createMarkupForProperty(propKey, propValue) 创建属性。
    • 通过 DOMPropertyOperations.createMarkupForID(this._domID) 创建唯一标识。
    2. 更新子节点

    当执行 mountComponent 方法时,ReactDOMComponent 会通过this._createContentMarkup(transaction, props, context) 来处理 DOM 节点的内容。

    当执行 receiveComponent 方法时,ReactDOMComponent 会通过 this._updateDOMChildren(lastProps, nextProps, transaction, context) 来更新 DOM 内容和子节点。

    先是删除不需要的子节点和内容。如果旧节点存在,而新节点不存在,说明当前节点在更新 后被删除,此时执行方法 this.updateChildren(null, transaction, context);如果旧的内容存在,而新的内容不存在,说明当前内容在更新后被删除,此时执行方法this.updateTextContent('')

    再是更新子节点和内容。如果新子节点存在,则更新其子节点,此时执行方法 this.updateChildren(nextChildren, transaction, context);如果新的内容存在,则更新内容,此时执行方法 this.updateTextContent('' + nextContent)

    六、自定义组件

    ReactCompositeComponent 自定义组件 实现了一整套 React 生命周期和 setState 机制,因此自定义组件是在生命周期的环境中进行更新属性、内容和子节点的操作。这些更新操作与ReactDOMComponent 的操作类似。


    七、总结

    React16版本之后,对Virtual DOM进行了更新,但主要思想还是一致的,想要了解的可参考React Fiber原理解析这篇文章。

    Virtual DOM和真实DOM的对比和适用场景

    首次渲染速度表现更新数据时速度表现适用场景
    Virtual DOM一般几乎都适用,在首次渲染和一些特殊场景下会稍慢,但是可以接受。真实DOM一般首次渲染和少量数据操作表现较好,数据量较大时更新操作较慢。

    Virtual DOM的性能优势并不完全的体现在“快”,而是体现在它还拥有着更出色的综合性能。

    参考文档:

    React列表中的key属性

    React Fiber原理解析

    Virtual DOM 及内核


    起源地下载网 » React中的Virtual DOM

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元