虚拟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中的一个工作过程,减少和控制不必要的重复渲染可以有效的提高页面性能。






原文地址:https://www.cnblogs.com/zhouyideboke/p/12700173.html

时间: 2024-11-06 03:46:01

虚拟DOM和Render函数的相关文章

react虚拟dom与diff算法

react拥有极速渲染的特点,这个特点依靠的就是react的虚拟dom和diff算法 对比两个图就可以发现标准dom机制下,用户在应用上的操作是直接对真实dom进行操作的,在react中我们操作 的是虚拟dom,用户的操作产生的数据改变或者state变量改变,都会保存到虚拟dom上,之后再批量的对这些更 改进行diff算法计算,对比操作前后的虚拟dom树,把更改后的变化再同步到真实dom上 虚拟DOM的原理: React会在内存中维护一个虚拟DOM树,对这个树进行读或写,实际上是对虚拟DOM进行

虚拟DOM 和 Diff 算法,key的作用,jsx,render函数

虚拟DOM 和 Diff 算法 什么是虚拟DOM? 使用javascript模拟了DOM结构的树形结构(对象表示),这个树结构包含整个DOM结构的信息 使用虚拟DOM有什么好处? 操作数据要大大的减少性能损耗,提高渲染效率 越多的真实dom操作,越损耗性能 什么是Diff 算法? 是linux的基础命令,用来比较两个文本文件的差异,是代码版本管理的基石之一 vdom中应用diff算法是为了找出需要更新的节点 diff算法的实现,关注patch,patch方法中首先判断两个节点是否相同 核心逻辑.

Virtual DOM 虚拟DOM的理解(转)

作者:戴嘉华 转载请注明出处并保留原文链接( #13 )和作者信息. 目录: 1 前言 2 对前端应用状态管理思考 3 Virtual DOM 算法 4 算法实现 4.1 步骤一:用JS对象模拟DOM树 4.2 步骤二:比较两棵虚拟DOM树的差异 4.3 步骤三:把差异应用到真正的DOM树上 5 结语 6 References 1 前言 本文会在教你怎么用 300~400 行代码实现一个基本的 Virtual DOM 算法,并且尝试尽量把 Virtual DOM 的算法思路阐述清楚.希望在阅读本

深入理解react中的虚拟DOM、diff算法

文章结构: React中的虚拟DOM是什么? 虚拟DOM的简单实现(diff算法) 虚拟DOM的内部工作原理 React中的虚拟DOM与Vue中的虚拟DOM比较 React中的虚拟DOM是什么?   虽然React中的虚拟DOM很好用,但是这是一个无心插柳的结果.   React的核心思想:一个Component拯救世界,忘掉烦恼,从此不再操心界面. 1. Virtual Dom快,有两个前提 1.1 Javascript很快  Chrome刚出来的时候,在Chrome里跑Javascript非

Vue2.x中的Render函数

Render函数是Vue2.x版本新增的一个函数:使用虚拟dom来渲染节点提升性能,因为它是基于JavaScript计算.通过使用createElement(h)来创建dom节点.createElement是render的核心方法.其Vue编译的时候会把template里面的节点解析成虚拟dom: 什么是虚拟dom? 虚拟dom不同于真正的dom,它是一个JavaScript对象.当状态发生变化的时候虚拟dom会进行一个diff判断/运算:然后判断哪些dom是需要被替换的而不是全部重绘,所以性能

React虚拟DOM具体实现——利用节点json描述还原dom结构

前两天,帮朋友解决一个问题: ajax请求得到的数据,是一个对象数组,每个对象中,具有三个属性,parentId,id,name,然后根据这个数据生成对应的结构. 刚好最近在看React,并且了解到其中的虚拟DOM,其实,就是利用json数据来代替DOM结构表示,然后利用这个json数据,渲染出DOM树,总体添加到页面中.下面,我就通过介绍我如何实现上面实际问题的思路,一边完成实际需求,一边实现React中虚拟DOM渲染成DOM的原理. 模拟数据结构如下: 1 var allJson = [{

为什么虚拟DOM更优胜一筹

注意: 虚拟DOM只是实现MVVM的一种方案,或者说是视图更新的一种策略.没有虚拟DOM比MVVM更好一说. 我们回顾传统MVC框架,如backbone,它是将某个模板编译成模板函数,需要更新时,是自己手动将数据整体传入模板函数, 得到一个字符串,使用innerHTML刷新某个容器!注意,这里其实可以优化,但由于是手动,是体力活,都是使用很粗放型的innerhTML了事 (使用jQuery的html方法性能会更差,不过好处是它处理了IE下的innerHTML BUG及全平台的无法执行内部的scr

vue2.0之render函数

虽然vue推荐用template来创建你的html,但是在某些时候你也会用到render函数. 虚拟DOM Vue 通过建立一个虚拟 DOM 对真实 DOM 发生的变化保持追踪.请近距离看一下这行代码: return createElement('h1', this.blogTitle) createElement 到底会返回什么呢?其实不是一个实际的 DOM 元素.它更准确的名字可能是createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,及其

Vue源码探究-虚拟DOM的渲染

Vue源码探究-虚拟DOM的渲染 在虚拟节点的实现一篇中,除了知道了 VNode 类的实现之外,还简要地整理了一下DOM渲染的路径.在这一篇中,主要来分析一下两条路径的具体实现代码. 按照创建 Vue 实例后的一般执行流程,首先来看看实例初始化时对渲染模块的初始处理.这也是开始 mount 路径的前一步.初始包括两部分,一是向 Vue 类原型对象上挂载渲染相关的方法,而是初始化渲染相关的属性. 渲染的初始化 下面代码位于vue/src/core/instance/render.js 相关属性初始