虚拟dom的实际运用

给后端团队封装定制一套jQuery UI组件,然后他们直接嵌数据渲染页面。可能你会想,前后分离为什么不让前端直接写页面接数据,我也很迷惑啊....说干就干。
先封装个基础的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">
            内容
        </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把它封装下

// 先创建外层的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)
// 省去下面代码....

一层一层的写下去,这代码有点多啊,我们得写个方法让它们去自己渲染生成dom。
实现思路:布局好的div字符串生成真实dom ⟶ 将真实dom转为虚拟dom ⟶ 对虚拟dom数据进行处理 ⟶ 将虚拟dom转为真实dom ⟶ 渲染页面 ⟶ 操作虚拟dom数据更新页面。
将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)  // 转虚拟dom

真实dom转为虚拟dom方法

// 真实dom转虚拟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数据



在dom字符串中会发现ahm-onclick,ahm-ondata等字段,这是预留的事件和数据渲染的处理,解析虚拟dom的时候将他运用上。

// 解析虚拟dom
function parseVnode(vnode) {
    let element = document.createElement(vnode.nodeName)
    vnode.element = element  // 保留dom节点后续方便dom操作
    for (let k in vnode.attr) {
        if (k.indexOf('ahm-') > -1) {  // 事件和数据的标记处理
            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: '内容',
    onOk: (e) => {},
    onCancel: (e) => {}
}
let dom = parseVnode.call(eventObj, vnode)
document.querySelector('body').appendChild(dom)

渲染完美结束,更新才是重点。
实现思路:新的虚拟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: '内容和颜色变了'
    }]
    updateVnode(vnode, patches)
}

简单的实现虚拟dom和更新的理解,当然更新方法和许多的事件,方法还有很多的情况,就此不一一判断了。可以参考react和vue的虚拟dom和diff算法的实现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。