React 虛擬DOM詳解

什么是虛擬DOM

react 中的 virtual DOM (虛擬DOM),其實(shí)就是JS對(duì)象。

眾所周知,瀏覽器的DOM元素的渲染效率極低,對(duì)DOM的優(yōu)化是前端開發(fā)人員一直以來很頭疼的問題,而虛擬DOM就是針對(duì)真實(shí)的DOM元素渲染效率低下而問世的。虛擬DOM在內(nèi)存中以JS對(duì)象的形式存在,模擬了真實(shí)DOM的所有結(jié)構(gòu),在將虛擬DOM渲染到頁面上之前,我們的所有操作都在虛擬DOM上進(jìn)行,你要知道:對(duì)JS對(duì)象的操作要比對(duì)DOM的操作快得多,所以虛擬DOM的出現(xiàn)使前端性能得到了極大的優(yōu)化。

虛擬DOM模 真實(shí)DOM的操作

假設(shè)我們有一個(gè)創(chuàng)建虛擬DOM的方法:createElement( tag, props, children )

createElement方法可以幫我們創(chuàng)建一個(gè)虛擬DOM,來模仿你想要的真實(shí)DOM的結(jié)構(gòu)

參數(shù):

  1. tag:DOM元素的標(biāo)簽( 'a' , 'p ', 'div' ...)
  2. props:DOM元素的屬性( {className:'box' , id:'container' , style:{fontSize:'20px'} , key:1 , ...})
  3. children:DOM元素的內(nèi)容( 字符串或其他虛擬DOM元素組成的數(shù)組 )
// 用 createElement 方法創(chuàng)建一個(gè)虛擬DOM的結(jié)構(gòu)
let virtualDOM = createElement('div',{id:'container'},[
    createElement('p',{className:'msg',key:1},'這是一條消息'),
    createElement('p',{className:'msg',key:2},'這是另一條消息'),
    createElement(null,null,'這是一條沒有標(biāo)簽包裹的文本'),
    createElement('button',{className:'btn',key:3},'按鈕')
]);

現(xiàn)在,很簡(jiǎn)單的一個(gè)虛擬DOM結(jié)構(gòu)已經(jīng)創(chuàng)建完成了,顯然,雖然它擁有自己的屬性和結(jié)構(gòu),但是目前為止這個(gè)虛擬DOM只是一個(gè)JS對(duì)象而已,我們對(duì)它進(jìn)行的任何操作都是在內(nèi)存中完成的(虛擬DOM性能好的原因)。但是我們最終需要的是一個(gè)真實(shí)的DOM,所以我們還需要一個(gè)render方法:render( virtualDOM, DOM )

render 方法可以將你的虛擬DOM解析成真實(shí)的DOM并渲染到頁面上

參數(shù):

  1. virtualDOM:需要解析的虛擬DOM
  2. DOM:需要渲染在哪個(gè)DOM里
render(virtualDOM,document.getElementById('root'));

至此,一個(gè)簡(jiǎn)單的 virtualDOM 模擬 真實(shí)DOM 的流程就結(jié)束了。

虛擬DOM原理

初始化:定義類型。

// 定義一個(gè)tag類型集合
const tagTypes = {
    HTML:"HTML",
    TEXT:"TEXT"
}


// 定義children類型集合
const childrenTypes = {
    // 子元素只有一個(gè)  說明是字符串
    single:"single",
    // 子元素是一個(gè)數(shù)組  數(shù)組里是多個(gè)元素
    many:"many",
    // 子元素是一個(gè)空  沒有子元素
    empty:"empty"
}

createElement()

實(shí)現(xiàn)原理:根據(jù)你傳入?yún)?shù)的類型,返回一個(gè)對(duì)應(yīng)出來你想要的結(jié)構(gòu)的整合后的JS對(duì)象。

// 創(chuàng)建虛擬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是文本的時(shí)候就創(chuàng)建一個(gè)文本虛擬dom
    // 如果children是數(shù)組的時(shí)候就創(chuàng)建一個(gè)有子節(jié)點(diǎn)的虛擬dom
    // 如果children是空的時(shí)候就創(chuàng)建一個(gè)空虛擬dom
    if(typeof children === 'string'){
        childrenType = childrenTypes.single;
        // createTextNode:創(chuàng)建文本DOM方法
        children = createTextNode(children)
    }else if(Array.isArray(children)){
        childrenType = childrenTypes.many;
    }else{
        childrenType = childrenTypes.empty;
    }

    // 返回虛擬dom對(duì)象
    return {
        el:null,
        type,
        tag,
        props,
        children,
        childrenType
    }
}

//創(chuàng)建文本虛擬dom,直接返回一個(gè)對(duì)應(yīng)的文本虛擬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已經(jīng)存在,那么執(zhí)行更新
        // 這一步是相當(dāng)復(fù)雜的diff算法,單獨(dú)開辟章節(jié)來講,此處暫時(shí)只考慮首次渲染
    }else{
        // 如果虛擬DOM沒有存在,那么執(zhí)行掛載(首次渲染)
        mounted(vnode, container);
    }

    // 判斷是初次渲染還是更新渲染
    container.vnode = vnode;
}

// 首次渲染函數(shù)
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元素,此處創(chuàng)建tag對(duì)應(yīng)的DOM元素并賦給el
    var el = document.createElement(tag);
    vnode.el = el;
    
    // 遍歷設(shè)置props屬性
    if(props){
        for(var key in props){
            /*  設(shè)置DOM的屬性(方法在代碼最后)
                語法:patchProps( 設(shè)置屬性的元素, 屬性的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 )
        })
    }

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

// 渲染文本虛擬dom的方法
function mountedText(vnode,container){
    // 創(chuàng)建一個(gè)對(duì)應(yīng)的文本節(jié)點(diǎn),直接插入父元素
    var textNode = document.createTextNode(vnode.children);
    vnode.el = textNode;
    container.appendChild(textNode);
}

// 掛載屬性的方法(部分情況)
// patchProps( 設(shè)置屬性的元素, 屬性的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);
            }

    }
}

至此,一個(gè)簡(jiǎn)單版的react中虛擬DOM的底層原理就實(shí)現(xiàn)啦

??ヽ(°▽°)ノ?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 40、React 什么是React?React 是一個(gè)用于構(gòu)建用戶界面的框架(采用的是MVC模式):集中處理VIE...
    萌妹撒閱讀 1,179評(píng)論 0 1
  • 文章結(jié)構(gòu): React中的虛擬DOM是什么? 虛擬DOM的簡(jiǎn)單實(shí)現(xiàn)(diff算法) 虛擬DOM的內(nèi)部工作原理 Re...
    李輕舟閱讀 3,191評(píng)論 2 14
  • 1.(Didact)一個(gè)DIY教程:創(chuàng)建你自己的react1.1 引言 2.渲染dom元素2.1 什么是DOM2....
    johnzhu12閱讀 826評(píng)論 0 51
  • 前言 在Jq,原生javascript時(shí)期,在寫頁面時(shí),往往強(qiáng)調(diào)的是內(nèi)容結(jié)構(gòu),層疊樣式,行為動(dòng)作要分離,三者之間分...
    itclanCoder閱讀 796評(píng)論 0 2
  • 1 我們十年前從別處搬來這里。 當(dāng)年來到這時(shí),建筑物前的兩棵透著靈氣大樹吸引了我們。春去秋來,它仍然這么蒼勁有力,...
    桔子的子閱讀 251評(píng)論 0 3

友情鏈接更多精彩內(nèi)容