keep-alive
在我们的平时开发工作中,经常为了组件的缓存优化而使用 组件,乐此不疲,但很少有人关注它的实现原理,下面就让我们来一探究竟。
内置组件
是 Vue 源码中实现的一个组件,也就是说 Vue 源码不仅实现了一套组件化的机制,也实现了一些内置组件,它的定义在 中:
可以看到 组件的实现也是一个对象,注意它有一个属性 为 true,是一个抽象组件,Vue 的文档没有提这个概念,实际上它在组件实例建立父子关系的时候会被忽略,发生在 的过程中:
在 钩子里定义了 和 ,本质上它就是去缓存已经创建过的 。它的 定义了 ,,它们可以字符串或者表达式, 表示只有匹配的组件会被缓存,而 表示任何匹配的组件都不会被缓存, 还定义了 ,它表示缓存的大小,因为我们是缓存的 对象,它也会持有 DOM,当我们缓存很多的时候,会比较占用内存,所以该配置允许我们指定缓存大小。
直接实现了 函数,而不是我们常规模板的方式,执行 组件渲染的时候,就会执行到这个 函数,接下来我们分析一下它的实现。
首先获取第一个子元素的 :
由于我们也是在 标签内部写 DOM,所以可以先获取到它的默认插槽,然后再获取到它的第一个子节点。 只处理第一个子元素,所以一般和它搭配使用的有 动态组件或者是 ,这点要牢记。
然后又判断了当前组件的名称和 、 的关系:
的逻辑很简单,就是做匹配,分别处理了数组、字符串、正则表达式的情况,也就是说我们平时传的 和 可以是这三种类型的任意一种。并且我们的组件名如果满足了配置 且不匹配或者是配置了 且匹配,那么就直接返回这个组件的 ,否则的话走下一步缓存:
这部分逻辑很简单,如果命中缓存,则直接从缓存中拿 的组件实例,并且重新调整了 key 的顺序放在了最后一个;否则把 设置进缓存,最后还有一个逻辑,如果配置了 并且缓存的长度超过了 ,还要从缓存中删除第一个:
除了从缓存中删除外,还要判断如果要删除的缓存并的组件 不是当前渲染组件 ,也执行删除缓存的组件实例的 方法。
最后设置 ,这个作用稍后我们介绍。
注意, 组件也是为观测 和 的变化,对缓存做处理:
逻辑很简单,观测他们的变化执行 函数,其实就是对 做遍历,发现缓存的节点名称和新的规则没有匹配上的时候,就把这个缓存节点从缓存中摘除。
组件渲染
到此为止,我们只了解了 的组件实现,但并不知道它包裹的子组件渲染和普通组件有什么不一样的地方。我们关注 2 个方面,首次渲染和缓存渲染。
同样为了更好地理解,我们也结合一个示例来分析:
首次渲染
我们知道 Vue 的渲染最后都会到 过程,而组件的 过程会执行 方法,它的定义在 中:
定义了 的变量,它是根据 以及 的判断,第一次渲染的时候, 为 , 为 true,因为它的父组件 的 函数会先执行,那么该 缓存到内存中,并且设置 为 true,因此 为 ,那么走正常的 的钩子函数执行组件的 。当 已经执行完 后,执行 函数:
这里会有 缓存了 创建生成的 DOM 节点。所以对于首次渲染而言,除了在 中建立缓存,和普通组件渲染没什么区别。
所以对我们的例子,初始化渲染 组件以及第一次点击 渲染 组件,都是首次渲染。
缓存渲染
当我们从 组件再次点击 切换到 组件,就会命中缓存渲染。
我们之前分析过,当数据发送变化,在 的过程中会执行 的逻辑,它会对比新旧 节点,甚至对比它们的子节点去做更新逻辑,但是对于组件 而言,是没有 的,那么对于 组件而言,如何更新它包裹的内容呢?
原来 在做各种 diff 之前,会先执行 的钩子函数,它的定义在 中:
核心逻辑就是执行 方法,它的定义在 中:
方法主要是去更新组件实例的一些属性,这里我们重点关注一下 部分,由于 组件本质上支持了 ,所以它执行 的时候,需要对自己的 ,也就是这些 做重新解析,并触发 组件实例 逻辑,也就是重新执行 的 方法,这个时候如果它包裹的第一个组件 命中缓存,则直接返回缓存中的 ,在我们的例子中就是缓存的 组件,接着又会执行 过程,再次执行到 方法,我们再回顾一下:
这个时候 为 true,并且在执行 钩子函数的时候不会再执行组件的 过程了,相关逻辑在 中:
这也就是被 包裹的组件在有缓存的时候就不会在执行组件的 、 等钩子函数的原因了。回到 方法,在 为 true 的情况下会执行 方法:
前面部分的逻辑是解决对 组件 动画不触发的问题,可以先不关注,最后通过执行 就把缓存的 DOM 对象直接插入到目标元素中,这样就完成了在数据更新的情况下的渲染过程。
生命周期
之前我们提到,组件一旦被 缓存,那么再次渲染的时候就不会执行 、 等钩子函数,但是我们很多业务场景都是希望在我们被缓存的组件再次被渲染的时候做一些事情,好在 Vue 提供了 钩子函数,它的执行时机是 包裹的组件渲染的时候,接下来我们从源码角度来分析一下它的实现原理。在渲染的最后一步,会执行 函数执行 的 钩子函数,它的定义在 中:
这里判断如果是被 包裹的组件已经 ,那么则执行 ,否则执行 。我们先分析非 的情况, 的定义在 中:
可以看到这里就是执行组件的 钩子函数,并且递归去执行它的所有子组件的 钩子函数。那么再看 的逻辑,它定义在 中:
这个逻辑很简单,把当前 实例添加到 数组中,等所有的渲染完毕,在 后会执行 ,这个时候就会执行:
也就是遍历所有的 ,执行 方法,通过队列调的方式就是把整个 时机延后了。有 钩子函数,也就有对应的 钩子函数,它是发生在 的 钩子函数,定义在 中:
对于 包裹的组件而言,它会执行 方法,定义在 中:
和 方法类似,就是执行组件的 钩子函数,并且递归去执行它的所有子组件的 钩子函数。
总结
那么至此, 的实现原理就介绍完了,通过分析我们知道了 组件是一个抽象组件,它的实现通过自定义 函数并且利用了插槽,并且知道了 缓存 ,了解组件包裹的子元素——也就是插槽是如何做更新的。且在 过程中对于已缓存的组件不会执行 ,所以不会有一般的组件的生命周期函数但是又提供了 和 钩子函数。另外我们还知道了 的 除了 和 还有文档中没有提到的 ,它能控制我们缓存的个数。
领取专属 10元无门槛券
私享最新 技术干货