React 虚拟DOM详解

什么是虚拟DOM

react 中的 virtual DOM (虚拟DOM),其实就是JS对象。

众所周知,浏览器的DOM元素的渲染效率极低,对DOM的优化是前端开发人员一直以来很头疼的问题,而虚拟DOM就是针对真实的DOM元素渲染效率低下而问世的。虚拟DOM在内存中以JS对象的形式存在,模拟了真实DOM的所有结构,在将虚拟DOM渲染到页面上之前,我们的所有操作都在虚拟DOM上进行,你要知道:对JS对象的操作要比对DOM的操作快得多,所以虚拟DOM的出现使前端性能得到了极大的优化。

虚拟DOM模 真实DOM的操作

假设我们有一个创建虚拟DOM的方法:createElement( tag, props, children )

createElement方法可以帮我们创建一个虚拟DOM,来模仿你想要的真实DOM的结构

参数:

  1. tag:DOM元素的标签( 'a' , 'p ', 'div' ...)
  2. props:DOM元素的属性( {className:'box' , id:'container' , style:{fontSize:'20px'} , key:1 , ...})
  3. children:DOM元素的内容( 字符串或其他虚拟DOM元素组成的数组 )
// 用 createElement 方法创建一个虚拟DOM的结构
let virtualDOM = createElement('div',{id:'container'},[
    createElement('p',{className:'msg',key:1},'这是一条消息'),
    createElement('p',{className:'msg',key:2},'这是另一条消息'),
    createElement(null,null,'这是一条没有标签包裹的文本'),
    createElement('button',{className:'btn',key:3},'按钮')
]);

现在,很简单的一个虚拟DOM结构已经创建完成了,显然,虽然它拥有自己的属性和结构,但是目前为止这个虚拟DOM只是一个JS对象而已,我们对它进行的任何操作都是在内存中完成的(虚拟DOM性能好的原因)。但是我们最终需要的是一个真实的DOM,所以我们还需要一个render方法:render( virtualDOM, DOM )

render 方法可以将你的虚拟DOM解析成真实的DOM并渲染到页面上

参数:

  1. virtualDOM:需要解析的虚拟DOM
  2. DOM:需要渲染在哪个DOM里
render(virtualDOM,document.getElementById('root'));

至此,一个简单的 virtualDOM 模拟 真实DOM 的流程就结束了。

虚拟DOM原理

初始化:定义类型。

// 定义一个tag类型集合
const tagTypes = {
    HTML:"HTML",
    TEXT:"TEXT"
}


// 定义children类型集合
const childrenTypes = {
    // 子元素只有一个  说明是字符串
    single:"single",
    // 子元素是一个数组  数组里是多个元素
    many:"many",
    // 子元素是一个空  没有子元素
    empty:"empty"
}

createElement()

实现原理:根据你传入参数的类型,返回一个对应出来你想要的结构的整合后的JS对象。

// 创建虚拟dom的方法
function createElment(tag,props,children){
    
    // 定义tag类型
    let type;
    
    // 如果tag存在,那么该元素就是HTML元素,否则是字符串
    if(typeof tag === 'string'){
        type = tagTypes.HTML;
    }else{
        type = tagTypes.TEXT;
    }

    // 定义children类型
    let childrenType;
    
    // 如果children是文本的时候就创建一个文本虚拟dom
    // 如果children是数组的时候就创建一个有子节点的虚拟dom
    // 如果children是空的时候就创建一个空虚拟dom
    if(typeof children === 'string'){
        childrenType = childrenTypes.single;
        // createTextNode:创建文本DOM方法
        children = createTextNode(children)
    }else if(Array.isArray(children)){
        childrenType = childrenTypes.many;
    }else{
        childrenType = childrenTypes.empty;
    }

    // 返回虚拟dom对象
    return {
        el:null,
        type,
        tag,
        props,
        children,
        childrenType
    }
}

//创建文本虚拟dom,直接返回一个对应的文本虚拟DOM
function createTextNode(text){
    return {
        type:'text',
        tag:null,
        props:null,
        children:text,
        childrenType:childrenTypes.empty
    }
}

render()

// 渲染方法
function render(vnode, container){

    if(container.vnode){
        // 如果虚拟DOM已经存在,那么执行更新
        // 这一步是相当复杂的diff算法,单独开辟章节来讲,此处暂时只考虑首次渲染
    }else{
        // 如果虚拟DOM没有存在,那么执行挂载(首次渲染)
        mounted(vnode, container);
    }

    // 判断是初次渲染还是更新渲染
    container.vnode = vnode;
}

// 首次渲染函数
function mounted(vnode,container){
    
    let {type} = vnode;
    if(type === 'HTML'){
        // 渲染HTML元素方法
        mountedElement(vnode,container)
    }else{
        // 渲染文本元素方法
        mountedText(vnode,container)
    }
}

// 渲染HTML元素的方法
function mountedElement( vnode, container ){
    let { type, tag, props, children, childrenType } = vnode;
    // el是真是的DOM元素,此处创建tag对应的DOM元素并赋给el
    var el = document.createElement(tag);
    vnode.el = el;
    
    // 遍历设置props属性
    if(props){
        for(var key in props){
            /*  设置DOM的属性(方法在代码最后)
                语法:patchProps( 设置属性的元素, 属性的key值, 旧的value值, 新的value )  */
            patchProps(el,key,null,props[key])
        }
    }
    
    // 判断该元素的子元素的类型
    if(childrenType === childrenTypes.single){
        // 如果 childrenType 属性为 single 那么肯定是文本,用渲染文本方法将子元素渲染
        mountedText(children, el)
    }else if(childrenType === childrenTypes.many){
        // 如果 childrenType 属性为 many 则肯定是嵌套子元素,遍历后用首次渲染方法将子元素渲染(递归)
        children.forEach((item)=>{
            mounted( item, el )
        })
    }

    // 渲染完成后,最终要插入到父级里面(最高父级就是root)
    container.appendChild(el);
}

// 渲染文本虚拟dom的方法
function mountedText(vnode,container){
    // 创建一个对应的文本节点,直接插入父元素
    var textNode = document.createTextNode(vnode.children);
    vnode.el = textNode;
    container.appendChild(textNode);
}

// 挂载属性的方法(部分情况)
// patchProps( 设置属性的元素, 属性的key值, 旧的value值, 新的value )
function patchProps(el, key, oldVal, newVal) {
    switch (key) {
        case 'className':
            el.className = newVal;
            break;
        case 'id':
            el.id = newVal;
            break;
        case 'onClick':
            el.addEventListener("click", newVal);
            break;
        case 'style': {
            for (var sKey in newVal) {
                el.style[sKey] = newVal[sKey];
            }
            break;
        }
        default:
            if (key != 'key') {
                el.setAttribute(key, newVal);
            }

    }
}

至此,一个简单版的react中虚拟DOM的底层原理就实现啦

✿✿ヽ(°▽°)ノ✿

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

推荐阅读更多精彩内容

  • 40、React 什么是React?React 是一个用于构建用户界面的框架(采用的是MVC模式):集中处理VIE...
    萌妹撒阅读 1,004评论 0 1
  • 文章结构: React中的虚拟DOM是什么? 虚拟DOM的简单实现(diff算法) 虚拟DOM的内部工作原理 Re...
    李轻舟阅读 2,999评论 2 14
  • 1.(Didact)一个DIY教程:创建你自己的react1.1 引言 2.渲染dom元素2.1 什么是DOM2....
    johnzhu12阅读 776评论 0 51
  • 前言 在Jq,原生javascript时期,在写页面时,往往强调的是内容结构,层叠样式,行为动作要分离,三者之间分...
    itclanCoder阅读 710评论 0 2
  • 1 我们十年前从别处搬来这里。 当年来到这时,建筑物前的两棵透着灵气大树吸引了我们。春去秋来,它仍然这么苍劲有力,...
    桔子的子阅读 215评论 0 3