vue页面更新patch的实现示例

组件页面渲染时,将render返回的新vnode(新节点)和组件实例保存的vnode(旧节点)作为参数,调用patch方法,更新DOM。

判断两个节点是否相同

处理过程中,需要判断节点是否相同。相同节点需要满足以下条件:

key相同

标签类型相同

注释节点标识相同,都是注释节点,或者都不是注释节点

data的值状态相同,或者都有值,或者都没值

function sameVnode (a, b) {// 判断两个VNode节点是否是同一个节点 return ( a.key === b.key && // key相同 ( a.tag === b.tag && // tag相同 a.isComment === b.isComment && // 注释节点标识相同 isDef(a.data) === isDef(b.data) && // data值状态相同 sameInputType(a, b) // input的type相同 ) ) }

patch方法

patch判断流程如下:

a) 如果新节点为空,此时旧节点存在(组件销毁时),调用旧节点destroy生命周期函数

b) 如果旧节点为空,根据新节点创建DOM

c) 其他(如果新旧节点都存在)

a) 旧节点不是DOM(组件节点),且新旧节点相同

执行patchVnode

b) 旧节点是DOM元素或者两个节点不相同

创建新节点DOM,销毁旧节点以及DOM。

function patch (oldVnode, vnode, hydrating, removeOnly) { if (isUndef(vnode)) { if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); } return } ... if (isUndef(oldVnode)) { isInitialPatch = true;// 组件初始加载 createElm(vnode, insertedVnodeQueue); } else { var isRealElement = isDef(oldVnode.nodeType); if (!isRealElement && sameVnode(oldVnode, vnode)) { patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly); } else { ... var oldElm = oldVnode.elm; var parentElm = nodeOps.parentNode(oldElm);// 获取父元素 // create new node createElm( vnode, insertedVnodeQueue, oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm)// 获取紧跟的弟弟元素 ); if (isDef(parentElm)) { removeVnodes(parentElm, [oldVnode], 0, 0);// 销毁旧节点以及DOM元素 } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode); } } } invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch); return vnode.elm } }

patchVnode方法

当两个节点相同时,执行patchVnode方法。在处理各种情况之前,会将旧节点elm属性值赋值给新节点的elm属性,保持elm保持一致。

具体流程如下:

a)如果新旧节点完全相同(引用相同 oldVnode === vnode)

直接返回不处理

b) 如果新节点不是文本节点

a)都存在子节点,新旧节点的子节点数组引用不同(oldCh !== ch)

updateChildren

b)新节点有子节点,旧节点没有

1)查重子节点(key)

2)如果旧节点是文本节点,先清空文本

3)创建子节点DOM元素

c)旧节点有子节点,新节点没有

移除子节点以及DOM

d)旧节点是文本节点

清除文本

c)如果新节点是文本节点,并且和旧节点文本不相同

则直接替换文本内容。

d)其他(新节点是文本节点,并且和旧节点相同)

不处理

function patchVnode ( oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly ) { if (oldVnode === vnode) { return } ... if (isUndef(vnode.text)) { if (isDef(oldCh) && isDef(ch)) { if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); } } else if (isDef(ch)) { if (process.env.NODE_ENV !== 'production') { checkDuplicateKeys(ch); } if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); } addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue); } else if (isDef(oldCh)) { removeVnodes(elm, oldCh, 0, oldCh.length - 1); } else if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); } } else if (oldVnode.text !== vnode.text) { nodeOps.setTextContent(elm, vnode.text); } ... }

updateChildren方法

updateChildren方法处理相同新旧节点的子节点。方法定义了以下变量(updateChildren的节点都表示的是子节点):

var oldStartIdx = 0;// 表示当前正在处理的旧起始节点序号 var newStartIdx = 0;// 表示当前正在处理的新起始节点序号 var oldEndIdx = oldCh.length - 1;// 表示当前正在处理的旧结尾节点序号 var oldStartVnode = oldCh[0];// 表示当前正在处理的旧起始节点 var oldEndVnode = oldCh[oldEndIdx];// 表示当前正在处理的旧结尾节点 var newEndIdx = newCh.length - 1;// 表示当前正在处理的新结尾节点序号 var newStartVnode = newCh[0];// 表示当前正在处理的新起始节点 var newEndVnode = newCh[newEndIdx];// 表示当前正在处理的新结尾节点 var oldKeyToIdx, // 尚未处理的旧节点key值映射 idxInOld, // 与新节点key值相同的旧节点序号 vnodeToMove, // 与新节点key值相同的旧节点 refElm;// 指向当前正在处理的新结尾节点的后一个节点(已处理)的DOM元素

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/cc8e9bb9bac894f78dcd3bc194ecdea6.html