其中,父组件中负责实例挂载的过程作为依赖会被执行,即执行父组件的vm._update(vm._render(), hydrating);_render_update分别代表两个过程,其中_render函数会根据数据的变化为组件生成新的Vnode节点,而_update最终会为新的Vnode生成真实的节点。而在生成真实节点的过程中,会利用vitrual domdiff算法对前后vnode节点进行对比,使之尽可能少的更改真实节点,这一部分内容可以回顾,里面详细阐述了利用diff算法进行节点差异对比的思路。

    patch是新旧Vnode节点对比的过程,而patchVnode是其中核心的步骤,我们忽略patchVnode其他的流程,关注到其中对子组件执行prepatch钩子的过程中。

    1. var componentVNodeHooks = {
    2. // 之前分析的init钩子
    3. init: function() {},
    4. prepatch: function prepatch (oldVnode, vnode) {
    5. // 新组件实例
    6. var options = vnode.componentOptions;
    7. // 旧组件实例
    8. var child = vnode.componentInstance = oldVnode.componentInstance;
    9. updateChildComponent(
    10. child,
    11. options.propsData, // updated props
    12. options.listeners, // updated listeners
    13. vnode, // new parent vnode
    14. options.children // new children
    15. );
    16. },
    17. }
    18. function updateChildComponent() {
    19. // 更新旧的状态,不分析这个过程
    20. ···
    21. // 迫使实例重新渲染。
    22. vm.$forceUpdate();
    23. }

    先看看$forceUpdate做了什么操作。$forceUpdate是源码对外暴露的一个api,他们迫使Vue实例重新渲染,本质上是执行实例所收集的依赖,在例子中watcher对应的是keep-alive的过程。

    由于vm.$forceUpdate()会强迫keep-alive组件进行重新渲染,因此keep-alive组件会再一次执行render过程。这一次由于第一次对vnode的缓存,keep-alive在实例的cache对象中找到了缓存的组件。

    1. // keepalive组件选项
    2. var keepAlive = {
    3. name: 'keep-alive',
    4. abstract: true,
    5. render: function render () {
    6. // 拿到keep-alive下插槽的值
    7. // 第一个vnode节点
    8. var vnode = getFirstComponentChild(slot);
    9. // 拿到第一个组件实例
    10. var componentOptions = vnode && vnode.componentOptions;
    11. // keep-alive的第一个子组件实例存在
    12. if (componentOptions) {
    13. // check pattern
    14. //拿到第一个vnode节点的name
    15. var name = getComponentName(componentOptions);
    16. var ref = this;
    17. var include = ref.include;
    18. var exclude = ref.exclude;
    19. // 通过判断子组件是否满足缓存匹配
    20. if (
    21. // not included
    22. (include && (!name || !matches(include, name))) ||
    23. // excluded
    24. (exclude && name && matches(exclude, name))
    25. ) {
    26. return vnode
    27. }
    28. var ref$1 = this;
    29. var cache = ref$1.cache;
    30. var keys = ref$1.keys;
    31. var key = vnode.key == null ? componentOptions.Ctor.cid + (componentOptions.tag ? ("::" + (componentOptions.tag)) : '')
    32. : vnode.key;
    33. // ==== 关注点在这里 ====
    34. if (cache[key]) {
    35. // 直接取出缓存组件
    36. vnode.componentInstance = cache[key].componentInstance;
    37. // keys命中的组件名移到数组末端
    38. remove(keys, key);
    39. } else {
    40. // 初次渲染时,将vnode缓存
    41. cache[key] = vnode;
    42. keys.push(key);
    43. if (this.max && keys.length > parseInt(this.max)) {
    44. pruneCacheEntry(cache, keys[0], keys, this._vnode);
    45. }
    46. }
    47. vnode.data.keepAlive = true;
    48. }
    49. return vnode || (slot && slot[0])
    50. }
    51. }

    执行了keep-alive组件的_render过程,接下来是_update产生真实的节点,同样的,keep-alive下有child1子组件,所以_update过程会调用createComponent递归创建子组件vnode,这个过程在初次渲染时也有分析过,我们可以对比一下,再次渲染时流程有哪些不同。

    此时的vnode是缓存取出的子组件vnode,并且由于在第一次渲染时对组件进行了标记vnode.data.keepAlive = true;,所以isReactivated的值为true,i.init依旧会执行子组件的初始化过程。但是这个过程由于有缓存,所以执行过程也不完全相同。

    1. var componentVNodeHooks = {
    2. init: function init (vnode, hydrating) {
    3. if (
    4. vnode.componentInstance &&
    5. !vnode.componentInstance._isDestroyed &&
    6. vnode.data.keepAlive
    7. ) {
    8. // 当有keepAlive标志时,执行prepatch钩子
    9. var mountedNode = vnode; // work around flow
    10. componentVNodeHooks.prepatch(mountedNode, mountedNode);
    11. } else {
    12. var child = vnode.componentInstance = createComponentInstanceForVnode(
    13. vnode,
    14. activeInstance
    15. );
    16. child.$mount(hydrating ? vnode.elm : undefined, hydrating);
    17. }
    18. },