[Vue source code] logic analysis of keep-alive components (in)

Start the growth journey of Nuggets! This is the 16th day I have participated in the “Nuggets Daily New Project · February Update Challenge”, click to view the event details

Before reading this chapter, it is recommended to read [[Vue source code]keep-alive component logic analysis (middle)]([Vue source code]keep-alive component logic analysis (on)). This chapter mainly analyzes the secondary rendering logic of Abstract Component and keep-alive

Abstract component

The built-in components provided by Vue all have an option to describe the type of the component. This option is {abstract: true } , which indicates that the component is an abstract component. So what is an abstract component and why the distinction is made.

  • Abstract components do not have real nodes, and will not be parsed and rendered into real DOM during rendering to welcome you, but only as an intermediate data transition layer, in keep-alive Medium is to process the component cache.
  • When the child component is initialized, the parent component instance will be mounted on the parent attribute of its own option, and the parent will be reversed during the initLifeCycle process on the vnode of the parent component, and add the vnode of the child component to its $children attribute, if you look up the parent in reverse In the process of components, if the parent component has the attribute abstract, it can be judged that the component is an abstract component. At this time, use the parent chain to continue searching until the component is not an abstract component. .
export function initLifecycle (vm: Component) {<!-- -->
  const options = vm. $options

  // locate first non-abstract parent
  let parent = options.parent
  if (parent & amp; & amp; !options. abstract) {<!-- -->
    // If the parent component has an abstract attribute, keep looking up until it is not an abstract component
    while (parent. $options. abstract & amp; & amp; parent. $parent) {<!-- -->
      parent = parent. $parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm

  vm. $children = []
  vm.$refs = {<!-- -->}

  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

This connection established between parent and child components is the basis for communication between parent and child components

Re-render the component

The re-rendering process starts with data changes, and data changes in dynamic components will cause the process of relying on dispatch updates. When the data changes, the collected dependencies will be dispatched and updated.

Among them, the process responsible for instance mounting in the parent component will be executed as a dependency, that is, vm._update(vm._render(), hydrating) of the parent component will be executed where _render The function will generate a new virtual DOM node for the component according to the data change, and _update will eventually generate a real node for the new virtual DOM node , and in the process of generating real nodes, the diff algorithm will be used to compare the new and old virtual DOM nodes, so as to change the real nodes as little as possible.

patch is the process of comparing the new and old virtual DOM, and patchVnode is the core step, here we mainly focus on executing prePatch< on subcomponents /code> hook process

function patchVnode (
  oldVnode,
  vnode,
  insertedVnodeQueue,
  ownerArray,
  index,
  removeOnly
) {<!-- -->
  //...
  if (isDef(data) & amp; & amp; isDef(i = data.hook) & amp; & amp; isDef(i = i.prepatch)) {<!-- -->
    i(oldVnode, vnode)
  }
}

When executing the prePatch hook, it will get the old and new component instances and execute the updateChildComponent function, and updateChildComponent will pair the old instance with the new component instance Update the state, including props listeners, etc., and finally call the global vm.$forceUpdate() provided by vue > method to re-render the instance.

const componentVNodeHooks = {<!-- -->

    prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {<!-- -->
    // instance of the new component
    const options = vnode. componentOptions
    // old component instance
    const child = vnode.componentInstance = oldVnode.componentInstance
    updateChildComponent(
      child,
      options.propsData, // updated props
      options. listeners, // updated listeners
      vnode, // new parent vnode
      options. children // new children
    )
  },
}

export function updateChildComponent (
  vm: Component,
  propsData: ?Object,
  listeners: ?Object,
  parentVnode: MountedComponentVNode,
  renderChildren: ?Array<VNode>
) {<!-- -->
  // update old state

  // resolve slots + force update if has children
  if (needsForceUpdate) {<!-- -->
    vm.$slots = resolveSlots(renderChildren, parentVnode.context)
    // Force the instance to re-render
    vm.$forceUpdate()
  }

  if (process.env.NODE_ENV !== 'production') {<!-- -->
    isUpdatingChildComponent = false
  }
}

Let's see what $forceUpdate does first. $forceUpdate is an api exposed by vue, which can make vue Instance re-rendering is essentially to execute the dependencies collected by the instance. For dynamic components wrapped by keep-alive, it is the vm that executes keep-alive ._update(vm._render(), hydrating) process

Vue.prototype.$forceUpdate = function () {<!-- -->
  var vm = this;
  if (vm._watcher) {<!-- -->
    vm._watcher.update();
  }
};

Reuse cache components

Since vm.$forceUpdate will force the keep-alive component to re-render, the keep-alive component will execute render again code> process, because the virtual DOM is cached in the first rendering, so the cached components will be found from the cache object when it is executed again.

// rendering function
render () {<!-- -->
  // Get the value of the slot under keep-alive
  const slots = this. $slots. default
  // Get the first vnode node
  const vnode: VNode = getFirstComponentChild(slot)
  // get the first component instance
  const componentOptions: ?VNodeComponentOptions = vnode & amp; & amp; vnode.componentOptions
  if (componentOptions) {<!-- -->
    // check pattern
    // Get the name attribute of the first subcomponent vnode
    const name: ?string = getComponentName(componentOptions)
    const {<!-- --> include, exclude } = this
    if (
      // Determine whether the subcomponent needs to be cached, and return the vnode object directly if no cache is required
      // not included
      (include & amp; & amp; (!name || !matches(include, name))) ||
      //excluded
      (exclude & amp; & amp; name & amp; & amp; matches(exclude, name))
    ) {<!-- -->
      return vnode
    }

    const {<!-- --> cache, keys } = this
    const key: ?string = vnode.key == null
      // same constructor may get registered as different local components
      // so cid alone is not enough (#3269)
      ?componentOptions.Ctor.cid + (componentOptions.tag ? `::${<!-- -->componentOptions.tag}` : '')
      : vnode.key
    if (cache[key]) {<!-- -->
      // Hit the cache,
      vnode.componentInstance = cache[key].componentInstance
      // make current key freshest
      // After deleting the key, add it again. The most recently used cache will be placed behind the array. This is the idea of the lru algorithm, which will be analyzed in detail later
      remove(keys, key)
      keys. push(key)
    } else {<!-- -->
      // first render, cache vnode
      cache[key] = vnode
      keys. push(key)
      // prune oldest entry
      if (this.max & amp; & amp; keys.length > parseInt(this.max)) {<!-- -->
        pruneCacheEntry(cache, keys[0], keys, this._vnode)
      }
    }

    // Add flags to the cache component
    vnode.data.keepAlive = true
  }
  return vnode || (slot & amp; & amp; slot[0])
}

When the render function of keep-alive is executed again, since the cache object stores the virtual DOM reading event, it directly passes the cache [key] Take out the cached component instance and assign it to the componentInstance property of vnode.

Replacement of real nodes

After executing the _render process of the keep-alive component, the next _update process produces real nodes, since keep-alive, so the _update process will call createComponent to recursively create subcomponents. Love your vnode, because it has already been rendered at the first time There is a cache, see what is the difference between rendering again and the first rendering

function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {<!-- -->
  let i = vnode.data
  if (isDef(i)) {<!-- -->
    // isReactivated is used to determine whether there is a cache in the component. When rendering again, there is a componentInstance attribute in vnode and vnode.data.keepAlive is true, so isReactivated is true
    const isReactivated = isDef(vnode.componentInstance) & amp; & amp; i.keepAlive
    if (isDef(i = i.hook) & amp; & amp; isDef(i = i.init)) {<!-- -->
      // Internal hook to perform component initialization init
      i(vnode, false /* hydrating */)
    }
    if (isDef(vnode. componentInstance)) {<!-- -->
      // One of the functions is to keep the real DOM in the virtual DOM
      initComponent(vnode, insertedVnodeQueue)
      insert(parentElm, vnode.elm, refElm)
      if (isTrue(isReactivated)) {<!-- -->
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
      }
      return true
    }
  }
}

In the createComponent method, the initialization process of the component will still be executed, that is, the init hook function of the component, but since this process already exists in the cache, the execution process is the same as the first Second initialization is not exactly the same

const componentVNodeHooks = {<!-- -->
  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {<!-- -->
    if (
      vnode.componentInstance & &
      !vnode.componentInstance._isDestroyed & &
      vnode.data.keepAlive
    ) {<!-- -->
      // When the cache already exists, execute the prePatch hook function
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks. prepatch(mountedNode, mountedNode)
    } else {<!-- -->
      // Copy the component instance to the componentInstance property of the virtual DOM
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      // Mount after creating the component instance
      child.$mount(hydrating? vnode.elm : undefined, hydrating)
    }
  },
}

Obviously, because of the keepAlive flag, the subcomponent is not going through the mounting process, but just executes the prePatch hook to update the state of the component, making good use of the cache The real nodes kept between vnode are replaced by nodes.

syntaxbug.com © 2021 All Rights Reserved.