React浅析(五):虚拟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)

输出打印的结果是:


打印结果.png

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

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


想不到吧.png

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官方声明周期图.png

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算法是非常复杂的,以上的方法只是一个解题思路,增删节点,重新排序,属性样式事件等变化,都是非常复杂的,在下能力有限,这里就不过多研究啦~有兴趣的同学自行找diff算法研究下咯

写在最后

这一节的主要讲的render函数在react中的一个工作过程,减少和控制不必要的重复渲染可以有效的提高页面性能,如果对virtual-dom 和diff算法感兴趣的话,后面会增加相关的学习内容,一起探讨啊~


搬砖使我快乐!.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容

  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom阅读 2,689评论 0 3
  • React Native 是最近非常火的一个话题,介绍如何利用 React Native 进行开发的文章和书籍多如...
    零度_不结冰阅读 663评论 0 1
  • 3. JSX JSX是对JavaScript语言的一个扩展语法, 用于生产React“元素”,建议在描述UI的时候...
    pixels阅读 2,797评论 0 24
  • 老大不在公司的第十八天。我和迪哥又说起了他,作为员工,替他担心,作为朋友,替他难过。心里最近的想法全都表露出来了,...
    花开兰亭叙阅读 190评论 0 1
  • 之一在冬天 在冬天 一个孩童 看见四条鱼在桶里游 他毫不犹豫地端起一壶开水 向桶里倒去 他想 这样鱼就不会被冻死了...
    闲不语阅读 123评论 4 4