虛擬dom的實際運用

給后端團隊封裝定制一套jQuery UI組件,然后他們直接嵌數(shù)據(jù)渲染頁面。可能你會想,前后分離為什么不讓前端直接寫頁面接數(shù)據(jù),我也很迷惑啊....說干就干。
先封裝個基礎的modal彈框組件:


彈框

HTML代碼:

<div class="ahm-modal-base-con">
    <div class="ahm-modal-base-cover"></div>
    <div class="ahm-modal-base-wrap">
        <div class="ahm-modal-base-top">
            <span class="ahm-modal-base-title">標題</span>
            <span class="ahm-modal-base-icon"><i class="iconfont icon-close"></i></span>
        </div>
        <div class="ahm-modal-base-center">
            內(nèi)容
        </div>
        <div class="ahm-modal-base-bottom">
            <div class="modal-base-bottom-btn-wrap">
                <button class="btn btn-site">確定</button>
                <button class="btn">取消</button>
            </div>
        </div>
    </div>
</div>

用js把它封裝下

// 先創(chuàng)建外層的div
dom = document.createElement('div')
dom.classList.add('ahm-modal-base-con')
document.querySelector('body').appendChild(dom)
// 遮罩
let coverDom = document.createElement('div')
coverDom.classList.add('ahm-modal-base-cover')
dom.appendChild(coverDom)
// 主布局部分
let baseDom = document.createElement('div')
baseDom.classList.add('ahm-modal-base-wrap')
dom.appendChild(baseDom)
// 省去下面代碼....

一層一層的寫下去,這代碼有點多啊,我們得寫個方法讓它們?nèi)プ约轰秩旧蒬om。
實現(xiàn)思路:布局好的div字符串生成真實dom ? 將真實dom轉(zhuǎn)為虛擬dom ? 對虛擬dom數(shù)據(jù)進行處理 ? 將虛擬dom轉(zhuǎn)為真實dom ? 渲染頁面 ? 操作虛擬dom數(shù)據(jù)更新頁面。
將dom字符串處理生成真實dom

// dom字符串
let domStr = `<div class="ahm-modal-base-con">
    <div class="ahm-modal-base-cover" style="z-index: 1001;"></div>
    <div class="ahm-modal-base-wrap" style="z-index: 1002; width: 600px;">
        <div class="ahm-modal-base-top">
            <span class="ahm-modal-base-title">標題</span>
            <span class="ahm-modal-base-icon"><i class="iconfont icon-close"></i></span>
        </div>
        <div class="ahm-modal-base-center" ahm-ondata = 'maskData'></div>
        <div class="ahm-modal-base-bottom">
            <div class="ahm-modal-base-btn-wrap">
                <button class="btn btn-site" ahm-onclick = 'onOk'>確定</button>
                <button class="btn" ahm-onclick = 'onCancel'>取消</button>
            </div>
        </div>
    </div>
</div>`

let objEle = document.createElement('div')
objEle.innerHTML = domStr
let domDir = objEle.childNodes[0]  // 獲得真實dom
let vnode = getVnode(domDir)  // 轉(zhuǎn)虛擬dom

真實dom轉(zhuǎn)為虛擬dom方法

// 真實dom轉(zhuǎn)虛擬dom
let __KEY = 0
function getVnode(domDir, parentVnode) {
    let vnode = null
    __KEY += 1

    if (domDir.nodeType == 1) {
        vnode = {
            nodeName: '',
            attr: {},
            value: '',
            _key: __KEY,
            children: [],
        }
        vnode['nodeName'] = domDir.localName

        for (let h of domDir.attributes) {
            vnode['attr'][h.name] = h.value
        }

        if (domDir.childNodes) {
            for (let k of domDir.childNodes) {
                let childNodes = getVnode(k, vnode)
                if (childNodes) {
                    vnode.children.push(childNodes)
                }
            }
        }

    } else if (domDir.nodeType == 3) {
        parentVnode['value'] = domDir.nodeValue.trim()
    }

    return vnode
}

得到虛擬dom數(shù)據(jù)



在dom字符串中會發(fā)現(xiàn)ahm-onclick,ahm-ondata等字段,這是預留的事件和數(shù)據(jù)渲染的處理,解析虛擬dom的時候?qū)⑺\用上。

// 解析虛擬dom
function parseVnode(vnode) {
    let element = document.createElement(vnode.nodeName)
    vnode.element = element  // 保留dom節(jié)點后續(xù)方便dom操作
    for (let k in vnode.attr) {
        if (k.indexOf('ahm-') > -1) {  // 事件和數(shù)據(jù)的標記處理
            let str = k.substring(4, k.length)
            if (typeof this[vnode.attr[k]] === 'function') {
                element[str] = this[vnode.attr[k]].bind(this)
            } else {
                element.innerHTML = this[vnode.attr[k]]
            }
        } else {
            element.setAttribute(k, vnode.attr[k])
        }
    }

    if (vnode.value) {
        element.innerHTML = vnode.value
    }

    if (vnode.children && vnode.children.length) {
        vnode.children.map(item => {
            element.appendChild(parseVnode.call(this, item))
        })
    }

    return element
}

拿到dom了,掛載到頁面去,掛載的事件記得補上。

let eventObj = {
    maskData: '內(nèi)容',
    onOk: (e) => {},
    onCancel: (e) => {}
}
let dom = parseVnode.call(eventObj, vnode)
document.querySelector('body').appendChild(dom)

渲染完美結(jié)束,更新才是重點。
實現(xiàn)思路:新的虛擬dom和舊的虛擬dom對比差別,把它們的所有變化記錄下來 ? 記錄下的補丁去遍歷樹更新dom。

//  省去新dom和舊dom的對比,我們直接得到需要更新的記錄patches
/*
 * 更新虛擬dom
 * @param vnode: 虛擬dom
 * @param patches: 需更新的補丁
 :patches = [{
        _key: 16,
        type: 'TEXT',
        attr: {
            style: "color: red"
        },
        value: this.num
    }]
*/
// 更新dom
function updateVnode(vnode, patches = []) {
    patches.map(patch => {
        _walk(vnode, patch)
    })
}
// 遍歷樹更新
function _walk(vnode = {}, patch) {
    if (vnode._key === patch._key) {
        if (patch.type === "TEXT") {
            vnode.value = patch.value
            Object.keys(patch.attr).map(d => {
                vnode.attr[d] = patch.attr[d]
            })

            if (vnode.attr && vnode.attr.style) {
                vnode.element.style = vnode.attr.style
            }
            vnode.element.innerHTML = patch.value
        }
        return
    }
    if (vnode.children) {
        vnode.children.map(item => {
            _walk(item, patch)
        })
    }
}

測試,對onOk事件完善下。

onOk: (e) => {
    let patches = [{
        _key: 16,
        type: 'TEXT',
        attr: {
            style: "color: red"
        },
        value: '內(nèi)容和顏色變了'
    }]
    updateVnode(vnode, patches)
}

簡單的實現(xiàn)虛擬dom和更新的理解,當然更新方法和許多的事件,方法還有很多的情況,就此不一一判斷了。可以參考react和vue的虛擬dom和diff算法的實現(xiàn)。

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

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