The combination of examples has the following situation

<keep-alive>
    <coma v-if="visible"></coma>
    <comb v-else></comb>
</keep-alive>
<button @click="visible = ! visible">To change the</button>
Copy the code

For example, coma and Comb both have an input that has a corresponding value. If we don’t use keep-alive, when we change visible, both of these components will be rendered again, and the previous input will be lost, BeforeCreate => created… . But if we use keep-alive, then the value of the input on the visible switch is the same as the value of the last change. So keep-alive is primarily used to keep a component in state and to prevent it from being created over and over again.

The principle of

The usage of keep-alive is defined in core/ Components /keep-alive

export default {
    abstract: true.props: {
        include: patternTypes, // Cache whitelist
        exclude: patternTypes,  // Cache the blacklist
        max: [String.Number] // The maximum number of instances in the cache
    },
    created() {
        // To cache the virtual DOM
        this.cache = Object.create(null);
        this.keys = [];
    },
    mounted() {
    // pruneCache is used to listen on the I whitelist if pruneCache is called
    // pruneCache updates the vUE cache
        this.$watch('include', val => {
            pruneCache(this, name => matches(val, name))
        })
        this.$watch('exclude', val => {
            pruneCache(this, name => ! matches(val, name)) }) } render() {/ /...}}Copy the code

The above code defines several operations to declare the cycle, the most important function render, let’s see how to implement

render

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

      const { cache, keys } = this
      constkey: ? string = vnode.key ==null // Define the cache key for the component
        // 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]) { // This component has been cached
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest
        remove(keys, key)
        keys.push(key) // Adjust the key sort
      } else {
        cache[key] = vnode // Cache component objects
        keys.push(key)
        // prune oldest entry
        if (this.max && keys.length > parseInt(this.max)) { // If the number of caches exceeds the limit, delete the first one
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
      }

      vnode.data.keepAlive = true // Rendering and executing the wrapped component's hook functions are required
    }
    return vnode || (slot && slot[0])}Copy the code

Perform a step-by-step analysis

  1. To obtainkeep-aliveObject contains the first child component object
  2. Returns its own based on whether the white blacklist matchesvnode
  3. According to thevnodethecidandtagThe generatedkey, whether there is a current cache in the cache object, returns if so, and updateskeyinkeysThe position of
  4. If the current cache object does not exist, go tocacheAdd the content of this, and according toLRUThe algorithm deletes instances that have not been used recently
  5. Is set to the first child component objectkeep-alivefortrue

For the first time to render

When rendering the page for the first time, we can get the data of the child component, then store the vNode information of the child component in cache, and set keepAlive of the coma component to true. We know that vNode is generated by the render function. We know that vNode is generated by the render function. The render function is defined in platforms/web/entry- Runtime -with-compiler, and the template is compiled to render by compileToFunctions

<template>
    <div class="parent">
        <keep-alive>
            <coma v-if="visible"></coma>
        <comb v-else></comb>
        </keep-alive>
    </div>
</template>
<script>
(function anonymous(a) {
  with(this) {
    return _c('div', {
      staticClass: "parent"
    }, [
      _c('keep-alive', [(visibility) ? _c('coma') : _c('comb')].1), 
      _c('button', {
      on: {
        "click": change
      }
    }, [_v("change")])], 1)}})</script>
Copy the code

You can see how keep-alive is generated in the generated Render function

 _c('keep-alive', [(visibility) ? _c('coma') : _c('comb')].1),
Copy the code

In keep-alive, _c(‘coma’) is used to access the componentOptions of the child component. _c is defined in vDOM/creation-element.js, and is used to determine whether the component is generated by vnode or other components.

To change thedataTo triggerpatch

On the first render, let’s change the input value in coma to see if the input remembers the previous value when visible changes to true again. Because if you change the value of visible, this code will be re-executed

updateComponent = (a)= > {
    vm._update(vm._render())
}
Copy the code

Therefore, the keep-alive function will be re-executed, since the data was stored in the cache during the first render, so this time the data will be fetched from the cache.

vnode.componentInstance = cache[key].componentInstance
Copy the code

When the key value does not exist in the first rendering, the vNode of the child component will be cached first. If the interruption point is used to see that the componentInstance is undefined in the first rendering, the vnode of the child component will be cached. ComponentInstance is actually generated by calling the init hook of the component in the patch process, so why can you get it at this time? Here is an example to explain such as the following example

a = {
    b: 1
}
c = a;
a.b = 5;
console.log(c.b) / / 5
Copy the code

Object is a reference type, so when the original object changes, the reference will also change. So the previous state information is assigned to coma, and then why is the value assigned to coma, coma will not execute the component creation process? Look at the patch code, When executed to createComponent, since coma is a component, the component-related logic is executed

// core/vdom/patch.js
function createComponent(vnode, insertedVnodeQueue, parentElm, refELm) {
    let i = vnode.data;
    if (isDef(i)) {
        const isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
        if (isDef(i = i.hook) && isDef(i = i.init)) {
            i(vnode, false); }}}// core/vdom/create-component
init(vnode) {
    if(vnode.componentInstance && ! vnode.componentInstance._isDetroyed && vnode.data.keepAlive) {const mountedNode: any = node;
            componentVnodeHooks.prepatch(mountedNode, mountedNode)
    } else {
        const child = vnode.componentInstance = createComponentInstanceForVnode(
            vnode,
            activeInstance
        )
        child.$mount(vnode.elm)
    }
}
Copy the code

Because vnode.ponentInstance has been reassigned to keepAlive and keepAlive is true, only prepatch is executed, so neither created nor mounted hooks are executed.

keep-aliveSelf created andpatchprocess

In core/ Instance /render, you can see the definition of updateComponent

updateComponent = (a)= > {
    vm._update(vm._render())
}
Copy the code

So first, we call the render function of keep-alive to generate vnode, and then we call vm._update to perform patch operation. So what are the differences between keep-alive and normal components when they are first created and during patch process?

For the first time to render

Whether keep-Alive is an abstract component or not, it is still a component, so it will execute the logic of the component, and perform the patch operation in core/ VDOM /patch when it is first rendered

function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
    let i = vnode.data
    if (isDef(i)) {
         const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
        if (isDef(i = i.hook) && isDef(i = i.init)) {
            i(vnode, false /* hydrating */)}}}Copy the code

Since this is the first time to render, the componentInstance does not exist, so we only execute the init hook, which creates the child componentInstance. But keep-alive components are abstract components. What makes an abstract component different from a normal component? As can be seen in core/ Instance/Lifecycle, components will only add themselves to the parent when they are not abstract and children will not add themselves to the abstract component $children. Again, this function is called from within vm._init

let parent = options.parent
  if(parent && ! options.abstract) {while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }
Copy the code

After changing the datapatchprocess

When visible changes, will the keep-alive component be affected? The Patch article mentioned that updateComponent is triggered when the value in the data changes

updateComponent = (a)= > {
    vm._update(vm._render())
}
Copy the code

The render function of keep-alive will be executed again, and the patch process of root component will be executed again. For detailed principle lessons, refer to the patch process of Vue source code. Here, the prepatch hook of keep-alive component is directly executed

To be solved

There is a problem that needs to be solved, the vnode needs to be regenerated every time it reaches the next tick, is there any way to optimize it, can it be replaced in another way, or does it have to be done? Can you think of any good ideas?

keep-aliveIs it necessary

As you can see, keep-alive is a great help in caching data and preventing components from being created repeatedly. Then there is the question of whether most components can use keep-alive to improve performance.

  1. What scenarios to use

    In the page, we refresh the data if we return to the previous page, if we want to preserve the state when we left the page, then we need to usekeep-alive
  2. What scenarios are not used

    Think about using firstkeep-aliveIs it necessary? If switching between two components does not require saving state, is it necessary? You might say yeskeep-aliveIt saves performance, which is what we needactivatedReset these properties. There are several risks to doing so
    1. Are you sure you reset all the variables, and that the risk is manageable
    2. All the caches are incacheWhen there are too many components, there is too much content, resulting in a large object, and it is doubtful that the performance needs to be improved

Vue’s source analysis article will be updated all the time, please keep an eye on my Github