zoukankan      html  css  js  c++  java
  • 虚拟DOM和Render函数

    虚拟DOM

             虚拟DOM(下面简化称为Vnode)简而言之 ,就是用js去描述一个dom节点树,而DOM变化的对比,都放在js层来做。

    传统的dom节点,是这样的

    <div>
        <p className='text'>写个啥内容啊</p>
    </div>
    Vnode是长这样的
    {
       nodeName:'div', //节点名字
       attributes:{},  //属性键值对
       children:[], //子节点
       key:undefined, //节点的唯一值
        ... 
    }

    为什么需要Vnode?

      这里,我们来引入一个传统的操作dom栗子。

    var arr = [1,2,3,4]
    function render(data){
       function createElement(tag){
          var dom = document.createElement(tag)
          return dom
       }
      var ul= createElement('ui')
      data.forEach((elem)=>{
          var liDom = createElement('li')
          liDom.innerHTML = elem
          ul.append(liDom)
      })
      return ul
    }
    render(arr)

    输出打印的结果是:

     

    但是这样操作dom的结果,当项目越大,页面交互越复杂,频繁的去操作dom,会导致页面卡顿,性能差,如何去减少dom操作是性能优化的一个关键点。

    千呼万唤的,Vnode可以解决这样的问题!!!


    Vnode是vue和react的核心。将DOM对比操作放在js层,提高效率。

    如何使用Vnode?

    首先vdom的两个核心api
    • h函数:用于生成vnode
    • path函数:

    h是指hyperscript,一种可以通过js来创建html的库。

    
    
    <div>
        <p className='text'>写个啥内容啊</p>
    </div>
    
    
    //经过babel编译,然后将它们传递给h函数调用
    h(
        'div',
        null,
        h('p',{className:'text'},'写个啥内容啊')
    )
    //react的React.createElement函数的作用就跟这里的h函数一样,结果是为了获得一个vnode,虚拟节点

    h函数输出的元素是一个dom节点的js对象,类似这样


    {
       'nodeName':'div',
       'attributes':{},
       'children':[...],
        'key':undefined,
        ...
    }
    h函数结束后,会调用render函数啦!!!


    Render函数

    前面我们提到了jsx是如何转换为虚拟dom的js对象,那么虚拟dom又是如何转为真实的DOM?

    这里需要思考两个问题:

    • render是什么?
    • 什么时候触发render?
    • render 的过程发生了什么?

    render是什么?

    写过React的人都知道,我们每个组件中有且只有一个render方法

    //class方式创建的组件
      class Home extends React.Component{
      //省略
        render(){
          return (
            <div>
              <p>一个节点</p>
            </div>
          )
        }
      }
    // 函数申明创建的组件
     function Page(){
       return (
            <div>
              <p>另一个节点</p>
            </div>
        )
      }  

    以上的代码栗子容易看出,无论是class方式还是函数申明方式创建出来的组件,返回的有且只有一个顶点节点。调用render方法,可以将react元素渲染到真实的dom中。

    什么时候触发render?

    在组件实例化和存在期时会执行render。

    从下图中可以看出:
    • 实例化过程中,当执行componentWillMount之后会执行render,开始将节点挂载在页面上。
    • 存在期的过程中,setState会导致组件的重新渲染。
      componentWillReceiveProps => shouldComponentUpdate => componentWillUpdate => render => componentDidUpdate
          

    React的重渲染机制,当状态更新后,我们只想让状态相关的组件重新渲染,并不喜欢其他不相关的组件被重渲染,对此也有相关的优化操作。shouldComponentUpdate(nextProps,nextState)方法中是render函数调用前执行的函数,开发者可以通过nextProps,nextState参数来判断当前场景是否需要重新渲染,当shouldComponentUpdate方法return true则重新渲染,return false则阻止组件渲染。

    同样,在PureComponent中,只接受props和state参数,如果props和state没有改变,PureComponent不会重渲染,可以一定程度上减少了render带来的消耗。

    render 的过程发生了什么?

    前面提到,React的核心虚拟DOM可以讲真实的dom节点以obj对象的形式来表示,通过对比新旧的obj对象的差异,更改页面相对应的变化节点。而React.render实际上就相当于是vdom里面的path函数,path函数接收两个参数。

    • 当首次渲染的时候,调用的是path(container,vnode)
    • 更新渲染的时候,调用的是 path(vnode,newVnode)

    以下例子,创建一个节点的实现思路(简易的)

    var vnode
    function render(data){
      var newVnode = h(....)//前面章节提到h函数,执行后返回一个虚拟的js对象,用来描绘dom节点的
    /*
    { 
      tag:’div’,
      attrs: {id:’’},
      children:[…]
    }
    */
      if(vnode){ 
      //如果节点已经存在,则重复渲染,将新旧节点传入path函数中,新旧对比
        path(vnode,newVnode)
      }else{
     //如果节点不存在,则首次渲染,将节点挂在在根节点container上
        path(container,newVnode)
      }
     // 将旧节点储存起来,便于下次新节点的新旧对比
      vnode = newVnode 
    }
    第一次渲染是如何进行?
    path(container,newVnode)

    // 创建一个真实节点
    function createElement(vnode){
        var tag = vnode.tag // 获取虚拟节点的tag类型
        var attrs = vnode.attrs|| [ ] // 储存虚拟节点的属性
        var children = vnode.children || [] // 储存虚拟节点的子节点
        if(!tag){
            return null
        }
        var elem = document.createElement(tag) // 创建一个真实的dom节点
        for(attrName in attrs){ //遍历所有属性,给真实节点添加属性
            if(atrs.hasOwnProperty(attrName)){
                elem.setAttribute(attrName,attrs[attrName])
            }
        }
        children.forEach(function(childVode){ //递归虚拟节点的子节点,创建节点追加到父节点中
            elem.appendChild(createElement(childVnode))
        })
        return elem
    }
    再次渲染是如何进行?
        path(vnode,newVnode)
     
    //更新渲染,通过对比新旧vnode,更新节点树
    function updateChildren (vnode,newVnode){
        var children = vnode.children || [ ]
        var newChildren = newVnode.children || [ ]    
        //遍历所有的children
        children.forEach(function (child,index){
            var newChild = newChildren[index]
            if(newChild==null){
                return
            }
            if(child.tag === newChild.tag){
                updateChildren(child,newChild)
            }else{
                replaceNode(child,newChild)
            }    
        })
    }

    path(container,vnode)和path(vnode,newVnode)的实现也是diff算法的一个实现过程,通过调用createElement和updateChildren方法让页面上的节点创建和更新。

    当然,真正的diff算法是非常复杂的。

     

    写在最后

    这一节的主要讲的render函数在react中的一个工作过程,减少和控制不必要的重复渲染可以有效的提高页面性能。

     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     




































  • 相关阅读:
    [ArcGIS API for JavaScript 4.8] Sample Code-Get Started-MapView,SceneView简介
    [C语言]易错知识点、小知识点复习(1)
    OpenID Connect Core 1.0(二)ID Token
    轻量ORM-SqlRepoEx 重大升级
    OpenID Connect Core 1.0(一)介绍
    轻量ORM-SqlRepoEx (五) 存储过程操作
    轻量ORM-SqlRepoEx (四)INSERT、UPDATE、DELETE 语句
    轻量ORM-SqlRepoEx (三)Select语句
    轻量ORM-SqlRepoEx (二)初始化SqlRepoEx
    轻量ORM-SqlRepoEx (一)SqlRepoEx介绍
  • 原文地址:https://www.cnblogs.com/zhouyideboke/p/12700173.html
Copyright © 2011-2022 走看看