好家伙,
本节来解决我们上一章留下来的问题,
新旧节点同时有儿子的情况本章继续解决
1.要做什么?
本章将解决,
1.在相同tag下子元素的替换问题
2.使用双指针进行元素替换,
实现效果如下:
let vm1 = new Vue({data:{name:'张三'}}) let render1 = compileToFunction(`<ul> <li style="background:yellow" key="c">我是黄色</li> </ul>`) let vnode1 = render1.call(vm1) document.body.appendChild(createELm(vnode1)) //数据更新 let vm2 = new Vue({data:{name:'李四'}}) let render2 = compileToFunction(`<ul> <li style="background:blue" key="c">我是蓝色</li> </ul>`) let vnode2 = render2.call(vm2) //patch 比对 setTimeout(()=>{ patch(vnode1,vnode2) },2000)
2.思路
let vm1 = new Vue({ data: { name: '张三' } }) let render1 = compileToFunction(`<ul> <li style="background:red" key="a">a</li> <li style="background:pink" key="b">b</li> <li style="background:blue" key="c">c</li> </ul>`) let vnode1 = render1.call(vm1) document.body.appendChild(createELm(vnode1)) //数据更新 let vm2 = new Vue({ data: { name: '李四' } }) let render2 = compileToFunction(`<ul> <li style="background:red" key="a">a</li> <li style="background:pink" key="b">b</li> <li style="background:blue" key="c">c</li> <li style="background:yellow" key="d">d</li> </ul>`) let vnode2 = render2.call(vm2) setTimeout(() => { patch(vnode1, vnode2) }, 2000)
我们用这个例子来举例
1.正序(从头开始)
找到不同(原先没有的)的项,再将它添加上去
大概的思路就是如此.
但同时,根据不同的情况
我们还有多种比对方法
2.2.逆序
2.3.交叉对比(从头)
2.4.交叉对比(从尾)
3.代码实现
3.1.双指针
//双指针 遍历 let oldStartIndex = 0 //老的开头索引 let oldStartVnode = oldChildren[oldStartIndex]; let oldEndIndex = oldChildren.length - 1 let oldEndVnode = oldChildren[oldEndIndex] let newStartIndex = 0 //新的开头索引 let newStartVnode = newChildren[newStartIndex]; let newEndIndex = newChildren.length - 1 let newEndVnode = newChildren[newEndIndex]
双指针的写法非常粗暴,但是好用
3.2.循环
(照着上面的图看)
while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) { //比对子元素 console.log(666) if (isSomeVnode(oldStartVnode, newStartVnode)) { //递归 debugger; //1 从头部开始 patch(oldStartVnode, newStartVnode); //移动指针 oldStartVnode = oldChildren[++oldStartIndex]; newStartVnode = oldChildren[++newStartIndex]; }//2 从尾部开始 else if(isSomeVnode(oldEndVnode, newEndVnode)){ // patch(oldEndVnode, newEndVnode); oldEndVnode = oldChildren[--oldEndIndex] newEndVnode = newChildren[--newEndIndex] }//3 交叉比对 从头 else if(isSomeVnode(oldStartVnode,newEndVnode)){ patch(oldStartVnode, newEndVnode); oldStartVnode =oldChildren[++oldStartIndex] newEndVnode = newChildren[--newEndIndex]; }//4 交叉比对 从尾 else if(isSomeVnode(oldEndVnode,newStartVnode)){ patch(oldEndVnode, newStartVnode); oldEndVnode =oldChildren[--oldStartIndex] newStartVnode = newChildren[++newStartIndex]; } }
3.3.isSomeVnode()
isSomeVnode()方法用于判断两个节点是否相同
function isSomeVnode(oldContext, newContext) { // return true return (oldContext.tag == newContext.tag) && (oldContext.key === newContext.key); }
3.4.添加多余的子儿子
//判断完毕,添加多余的子儿子 例子:旧的a b c 新的 a b c d 将d添加到parent if (newStartIndex <= newEndIndex) { for (let i = newStartIndex; i <= newEndIndex; i++) { parent.appendChild(createELm(newChildren[i])) } }
搞定
4.patch.js完整代码
如下:
export function patch(oldVnode, Vnode) { debugger; //原则 将虚拟节点转换成真实的节点 // console.log(oldVnode, Vnode) // console.log(oldVnode.nodeType) // console.log(Vnode.nodeType) //第一次渲染 oldVnode 是一个真实的DOM //判断ldVnode.nodeType是否为一,意思就是判断oldVnode是否为属性节点 if (oldVnode.nodeType === 1) { console.log(oldVnode, Vnode) //注意oldVnode 需要在加载 mount 添加上去 vm.$el= el let el = createELm(Vnode) // 产生一个新的DOM let parentElm = oldVnode.parentNode //获取老元素(app) 父亲 ,body // console.log(oldVnode) // console.log(parentElm) parentElm.insertBefore(el, oldVnode.nextSibling) //当前真实的元素插入到app 的后面 parentElm.removeChild(oldVnode) //删除老节点 //重新赋值 return el } else { // diff // console.log(oldVnode.nodeType) console.log(oldVnode, Vnode) //1 元素不是一样 if (oldVnode.tag !== Vnode.tag) { //旧的元素 直接替换为新的元素 return oldVnode.el.parentNode.replaceChild(createELm(Vnode), oldVnode.el) } //2 标签一样 text 属性 <div>1</div> <div>2</div> tag:undefined if (!oldVnode.tag) { if (oldVnode.text !== Vnode.text) { return oldVnode.el.textContent = Vnode.text } } //2.1属性 (标签一样) <div id='a'>1</div> <div style>2</div> //在updataRpors方法中处理 //方法 1直接复制 let el = Vnode.el = oldVnode.el updataRpors(Vnode, oldVnode.data) //diff子元素 <div>1</div> <div></div> let oldChildren = oldVnode.children || [] let newChildren = Vnode.children || [] if (oldChildren.length > 0 && newChildren.length > 0) { //老的有儿子 新有儿子 //创建方法 updataChild(oldChildren, newChildren, el) } else if (oldChildren.length > 0 && newChildren.length <= 0) { //老的元素 有儿子 新的没有儿子 el.innerHTML = '' } else if (newChildren.length > 0 && oldChildren.length <= 0) { //老没有儿子 新的有儿子 for (let i = 0; i < newChildren.length; i++) { let child = newChildren[i] //添加到真实DOM el.appendChild(createELm(child)) } } } } function updataChild(oldChildren, newChildren, parent) { //diff算法 做了很多优化 例子<div>11</div> 更新为 <div>22</div> //dom中操作元素 常用的 思想 尾部添加 头部添加 倒叙和正序的方式 //双指针 遍历 let oldStartIndex = 0 //老的开头索引 let oldStartVnode = oldChildren[oldStartIndex]; let oldEndIndex = oldChildren.length - 1 let oldEndVnode = oldChildren[oldEndIndex] let newStartIndex = 0 //新的开头索引 let newStartVnode = newChildren[newStartIndex]; let newEndIndex = newChildren.length - 1 let newEndVnode = newChildren[newEndIndex] console.log(oldEndIndex,newEndIndex) console.log(oldEndVnode,newEndVnode) while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) { //比对子元素 console.log(666) if (isSomeVnode(oldStartVnode, newStartVnode)) { //递归 debugger; //1 从头部开始 patch(oldStartVnode, newStartVnode); //移动指针 oldStartVnode = oldChildren[++oldStartIndex]; newStartVnode = oldChildren[++newStartIndex]; }//2 从尾部开始 else if(isSomeVnode(oldEndVnode, newEndVnode)){ // patch(oldEndVnode, newEndVnode); oldEndVnode = oldChildren[--oldEndIndex] newEndVnode = newChildren[--newEndIndex] }//3 交叉比对 从头 else if(isSomeVnode(oldStartVnode,newEndVnode)){ patch(oldStartVnode, newEndVnode); oldStartVnode =oldChildren[++oldStartIndex] newEndVnode = newChildren[--newEndIndex]; }//4 交叉比对 从尾 else if(isSomeVnode(oldEndVnode,newStartVnode)){ patch(oldEndVnode, newStartVnode); oldEndVnode =oldChildren[--oldStartIndex] newStartVnode = newChildren[++newStartIndex]; } } //判断完毕,添加多余的子儿子 a b c 新的 a b c d console.log(newEndIndex) if (newStartIndex <= newEndIndex) { for (let i = newStartIndex; i <= newEndIndex; i++) { parent.appendChild(createELm(newChildren[i])) } } } function isSomeVnode(oldContext, newContext) { // return true return (oldContext.tag == newContext.tag) && (oldContext.key === newContext.key); } //添加属性 function updataRpors(vnode, oldProps = {}) { //第一次 let newProps = vnode.data || {} //获取当前新节点 的属性 let el = vnode.el //获取当前真实节点 {} //1老的有属性,新没有属性 for (let key in oldProps) { if (!newProps[key]) { //删除属性 el.removeAttribute[key] // } } //2演示 老的 style={color:red} 新的 style="{background:red}" let newStyle = newProps.style || {} //获取新的样式 let oldStyle = oldProps.style || {} //老的 for (let key in oldStyle) { if (!newStyle[key]) { el.style = '' } } //新的 for (let key in newProps) { if (key === "style") { for (let styleName in newProps.style) { el.style[styleName] = newProps.style[styleName] } } else if (key === 'class') { el.className = newProps.class } else { el.setAttribute(key, newProps[key]) } } } //vnode 变成真实的Dom export function createELm(vnode) { let { tag, children, key, data, text } = vnode //注意 if (typeof tag === 'string') { //创建元素 放到 vnode.el上 vnode.el = document.createElement(tag) //创建元素 updataRpors(vnode) //有儿子 children.forEach(child => { // 递归 儿子 将儿子渲染后的结果放到 父亲中 vnode.el.appendChild(createELm(child)) }) } else { //文本 vnode.el = document.createTextNode(text) } return vnode.el //新的dom }
标签:el,Vue,oldChildren,Vnode,oldVnode,let,newChildren,diff,源码 From: https://www.cnblogs.com/FatTiger4399/p/17814677.html