给后端团队封装定制一套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算法的实现。