The original design of Keep-Alive

Some business scenarios need to dynamically switch between multiple components based on different judgment criteria. Frequent component switching can lead to repeated rendering of components, which can easily cause performance problems if a component contains a large number of logical and DOM nodes. Second, the state of the component is completely lost after the switch. Keep-alive is designed to preserve the state of components and avoid repeated rendering of components.

Why can keep-alive be used directly

Developers do not need to register and import, and can use it directly in the template. Unlike Vue.com Ponent’s custom components, keep-Alive does not require registration and can be used directly in the template, as shown below:

<keep-alive>
  <component :is="view"></component>
</keep-alive>
Copy the code

This is because keep-Alive is a built-in component of VUE and has been defined in advance in VUE.

// core/components/keep-alive.js

export default {
  name: 'keep-alive'.abstract: true.props: {
    include: patternTypes,
    exclude: patternTypes,
    max: [String.Number]
  },

  created () {
    this.cache = Object.create(null)
    this.keys = []
  },

  destroyed () {
    // Keep-alive destruction removes all cached components
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },

  mounted () {
    // If the include and exclude attributes are specified, you need to observe the changes of the current two attributes in real time and update the cache in time
    this.$watch('include'.val= > {
      pruneCache(this.name= > matches(val, name))
    })
    this.$watch('exclude'.val= > {
      pruneCache(this.name= >! matches(val, name)) }) }, render () {// The keepAlive component itself is not rendered as a DOM node; the render method handles the logic by returning the vNode of the wrapped component
    const slot = this.$slots.default
    // Get the first component child node
    const vnode: VNode = getFirstComponentChild(slot)
    constcomponentOptions: ? VNodeComponentOptions = vnode && vnode.componentOptionsif (componentOptions) {
      // check pattern
      constname: ? string = getComponentName(componentOptions)const { include, exclude } = this
      if (
        // not included(include && (! name || ! matches(include, name))) ||// excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }

      const { cache, keys } = this
      constkey: ? 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

      // 1. If the vnode exists in the cache, take the instance of the component from the cache (one component corresponds to a VNode tree, and one component corresponds to an instance of a VUE subclass) and do not recreate it
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest
        // Use the current component's key as the latest cache (updating its order in the keys array)
        remove(keys, key)
        keys.push(key)
      } else {
        // if not, add it to cache
        cache[key] = vnode
        keys.push(key)
        // If the cache exceeds the limit, the oldest cache is discarded
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
      }

      // mark as keepAlive component
      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])}}Copy the code
// core/components/index.js
import KeepAlive from './keep-alive'

export default {
  KeepAlive
}

Copy the code
// core/global-api/index.js

// Import built-in components
import builtInComponents from '.. /components/index'

/** * Add global methods and attributes * to Vue@param {GlobalAPI} Vue 
 */
export function initGlobalAPI (Vue: GlobalAPI) {
  
  / /... Extraneous code is omitted
  
  Vue.options = Object.create(null)
  // Add the built-in keep-alive component
  extend(Vue.options.components, builtInComponents)
}
Copy the code

BuildInComponents contains the definition of keep-alive. In the initGlobalAPI method, the built-in components are added to the global variables of the vue.

Extend (A, B) is A simple object property copy method. Copy properties from object B to object A.

How does keep-alive keep components in state

To maintain component state, keep-Alive designs a caching mechanism.

We know that each HTML tag in the template is represented in the VUE by the corresponding VNode node object. If the HTML tag is a component tag, you need to create a component instance for the VNode of the tag. Component instances are responsible for compiling and rendering HTML templates within components. Therefore, compared to vnodes with normal HTML tags, component vnodes have two properties: componentOptions and componentInstance, which hold reference to componentOptions objects and component instances.

First, we can see from the implementation code of the Keep-alive component that the caching mechanism is designed in the component’s Created hook:

created () {
    this.cache = Object.create(null)
    this.keys = []
}
Copy the code

Keep-alive sets the cache and keys properties to cache child components. Each item in the cache is an object whose value is the component name key of the wrapped component and the corresponding vnoded value of the wrapped component. Each entry of keys is the component name of the component it wraps around.

The Render function is the method used to create vNodes in vUE instances and vUE component instances. In practice, templates are specified by template and EL, and vue compiles the template into the render function. Custom render functions can be provided if users want more control over vNode creation.

render () {
    const slot = this.$slots.default
    // Get the first component child node
    const vnode: VNode = getFirstComponentChild(slot)
    constcomponentOptions: ? VNodeComponentOptions = vnode && vnode.componentOptionsif (componentOptions) {
      // check pattern
      constname: ? string = getComponentName(componentOptions)const { include, exclude } = this
      if (
        // not included(include && (! name || ! matches(include, name))) ||// excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }

      const { cache, keys } = this
      constkey: ? 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

      // 1. If the vnode exists in the cache, take the instance of the component from the cache (one component corresponds to a VNode tree, and one component corresponds to an instance of a VUE subclass) and do not recreate it
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest
        // Use the current component's key as the latest cache (updating its order in the keys array)
        remove(keys, key)
        keys.push(key)
      } else {
        // if not, add it to cache
        cache[key] = vnode
        keys.push(key)
        // If the cache exceeds the limit, the oldest cache is discarded
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
      }

      // mark as keepAlive component
      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])}Copy the code

Inside keep-alive is a separate render function that defines the vNode creation logic. Keep-alive first retrieves the root VNode of the wrapped child component, and then checks the cache to see if the component exists.

If the child vnode does not exist in the cache, it is saved to the cache object as {child component name: child vnode}. Also save the child component names into the keys array. If the current number of caches exceeds the maximum value set by Max, the least recently used cache entry (LRU) needs to be eliminated.

If a component vnode exists in the cache, only the componentInstance (componentInstance) of the cached component vnode is reused. This subcomponent, vNode, needs to be moved to the front of the cache in order to properly flush out cached items when the cache is running low.

For example

Finally, an example is given to deepen our understanding.

 <div id="app">
    <keep-alive><component :is="view"></component></keep-alive>
    <button @click="view = view =='count'? 'any': 'count'">Switch components</button>
</div>
Copy the code
Vue.component("count", {
    data() {
        return {
            count:0
        };
    },
    template: 
      
click on me {{count}} times
}); Vue.component("any", { template: "<div>any</div>" }); new Vue({ el: "#app".data: { view: "count"}});Copy the code

Since the view default value is count, the child component of the keep-alive package is count. The keep-alive cache is empty, so the vNode of the component count is added to the cache. The cache result is:

cache = {1::count: {tag: "vue-component-1-count".data: {tag: "component".hook: {... }}}, componentOptions, componentInstance, ... } keys = ["1::count"]
Copy the code

The following information is displayed:

Click on component Count and the component displays “clicked on me once”, then switch to component any. As with the count component, any needs to be added to the cache because the VNode for any component has not been saved in the keep-alive cache. The cache result now becomes:

cache = {
    1::count: {tag: "vue-component-1-count".data: {tag: "component".hook: {... }}, componentOptions, componentInstance, ... },2::any: {tag: "vue-component-2-any".data: {tag: "component".hook: {... }}, componentOptions, componentInstance, ... }, } keys = ["1::count"."2::any"]
Copy the code

The following information is displayed:

Click toggle components again to cut back to Count. At this time, the vnode of the count component already exists in the cache, so the component instance saved in the vnode of the original count component is directly reused, and the original count value is saved in the component instance. Therefore, after the component switchover, the state of the component is also restored.

The following figure forcountThe state of the component instance can be seencountKeep up with:

The final page displays:

From the above analysis, if the component is wrapped in a keep-alive component, the component vNode is cached in the cache. The state of the component will be saved in the componentInstance. When the component switches back again, keep-alive directly restores the state cached before, thus realizing the maintenance of the component state.

Pay attention to our