Vue.js 3.0源码解析(微课视频版)
上QQ阅读APP看书,第一时间看更新

2.5 patch()函数

首次渲染完成后,当页面更新时,需通过patch()函数对新旧VNode进行对比处理,以最小代价完成页面更新。Vue3对patch()函数也进行了优化和调整,本节介绍的patch()函数与Vue3内应用的patch()函数逻辑有所不同,但核心思路基本一致。patch()函数内会按照如下逻辑判断修改内容:

(1)新旧节点完全不同,直接卸载旧节点,调用mount()函数挂载新节点;

(2)判断新旧节点props(属性)是否有变化;

(3)判断子节点是否有变化。

下面逐项进行介绍,完成整个patch()函数的理解。

首先,通过VNode的tag属性判断新旧节点是否完全不同。VNode节点在初始化时,会依据结构类型设置tag属性,如果该属性变化,证明整个节点已经变化,直接卸载旧节点,渲染新节点即可。若相同,则进行下一步判断。具体代码如下:

其次,通过对比新旧props的属性值判断props属性是否有变化。执行如下处理流程:

(1)判断新VNode的props属性在旧VNode中的覆盖情况。通过新VNode的props为基准进行遍历匹配,在匹配过程中若遇到新旧props不一致,且新props的属性值不为空,则在DOM节点新增该props;

(2)若遇到新旧props相同,但新props属性值为空,则需在旧props中删除该属性值。完成对新props属性的遍历后,实现以新props为基准,完成在新props中新增内容和在旧props中删除内容的初步调整;

(3)此时还未对在旧props内存在,但在新props内不存在的属性进行处理。因此仍需遍历旧props,判断是否在旧props内存在,但在新props内不存在的属性,若存在则执行删除操作。

该遍历执行后完成整个新旧props属性的判断和更新,具体代码实现如下:

此处直接通过一刀切的方式强制调整了VNode属性值的变化。在Vue3中,整个patch()的实现逻辑更加复杂,还将引入分而治之、节点优化等措施,以减少DOM节点的变化。

细心的读者可能会发现,在代码第一行进行了特殊操作。将n1(旧)的DOM结构赋值给n2(新)的DOM结构,此处不是代码书写问题,而是在赋值前已经对比过tag相同,说明两个VNode元素只是内部属性值不同,完全可以沿用当前的DOM结构。此处进行赋值后,在进行属性值调整时,只需要直接更新即可。通过该方式巧妙地完成整个属性值的diff。

完成props属性的处理后,还剩下子节点需要处理,子节点的值类型有两种情况:字符串或数组。

若为字符串类型,则代表只是值的变化,例如:

     <div@click="clickHandle">hello vue3</div>

对<div@click="clickHanlde"></div>处理已完成,hello vue3这个子节点即为字符串类型,此时只需要修改字符串为最新的即可,代码实现如下:

若为数组类型,则不可避免地需要通过循环进行处理。数组内可能会有字符串和数组在一起的情况,此时需要进一步判断当前元素是字符串还是数组。

若为字符串,则直接清空现有的字符串,调用mount()挂载即可。

若为数组,则需要进一步patch新旧VNode的情况,执行patch递归处理。代码实现如下:

若新节点为数组,则遍历时先取新旧节点数组长度的交集部分,对相同位置的子节点进行patch操作,再根据数组长度判断剩下的部分是旧节点还是新节点,若是旧节点直接通过forEach遍历进行删除;若是新节点,则通过mount()进行挂载。

该流程处理后,整个patch()的过程均已完成,需要进行增、删、改的节点也完成调整。通过diff对比VNode的更新情况,完成真实DOM结构的调整,整个patch流程如图2.3所示。

图2.3 patch流程