虚拟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算法的实现。

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