preface

There is a particularly useful component in Vue, the Keep-alive component, we can use this component in many scenarios to improve our product experience, basically realize the cache effect at zero cost. The most commonly used scenario is to cache less-updated routing pages with routing.

So easy to use the function, behind how to achieve, what can be learned, together to analyze.

The text analysis

What

Introduction from the official website cn.vuejs.org/v2/api/#kee…

As you can see, its use is more closely followed with dynamic components. The core is to cache component instances to improve performance. There is also a TAB switch example on the official website, which is one of its use scenarios cn.vuejs.org/v2/guide/co…

How

Since it is a built-in component, it must also be a component-defined implementation of the core in Vue at github.com/vuejs/vue/b… here

export default {
  // Component name
  name: 'keep-alive'.This is an unexposed component declaration property
  // The DOM will not appear in the children of the parent component if it is not rendered
  abstract: true.props: {
    include: patternTypes,
    exclude: patternTypes,
    max: [String.Number]},methods: {
    cacheVNode() {
      const { cache, keys, vnodeToCache, keyToCache } = this
      // A node needs to be cached
      if (vnodeToCache) {
        const { tag, componentInstance, componentOptions } = vnodeToCache
        // Cache to the cache object
        cache[keyToCache] = {
          name: getComponentName(componentOptions),
          tag,
          componentInstance,
        }
        keys.push(keyToCache)
        // Determine whether the maximum number of cache instances is exceeded
        // prune oldest entry
        if (this.max && keys.length > parseInt(this.max)) {
          // If it exceeds, destroy it
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
        this.vnodeToCache = null
      }
    }
  },
 
  created () {
    // Initialize the cache object
    this.cache = Object.create(null)
    this.keys = []
  },
 
  destroyed () {
    // All instances are destroyed
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },
 
  mounted () {
    this.cacheVNode()
    // Check again if all cache instances should be cached if they are updated
    this.$watch('include'.val= > {
      pruneCache(this.name= > matches(val, name))
    })
    this.$watch('exclude'.val= > {
      pruneCache(this.name= >! matches(val, name)) }) }, updated () {// Update the hook cache again
    this.cacheVNode()
  },
 
  render () {
    // focus the implementation of render
    // You can get the default contents of the component, which is the default slot contents
    const slot = this.$slots.default
    // Find the first component node
    const vnode: VNode = getFirstComponentChild(slot)
    // Component configuration items can be obtained from the vNode
    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
      if (cache[key]) {
        // It was cached before
        // Use the previously cached component instance directly
        vnode.componentInstance = cache[key].componentInstance
        // Delete the key first and then use it when pushing to ensure that the key is a fresh out-of-limit check
        // make current key freshest
        remove(keys, key)
        keys.push(key)
      } else {
        // delay setting the cache until update
        // Set vnodeToCache to be cached when updated refer to the logic in the updated hook
        this.vnodeToCache = vnode
        this.keyToCache = key
      }
 
      vnode.data.keepAlive = true
    }
    // Return the internal node
    return vnode || (slot && slot[0])}}Copy the code

This is probably the core flow:

  • By default it comes in and gets the currently rendered VNode
  • Then enter mounted hook on cache
  • When there is an update, call Render again and set vnodeToCache
  • To the updated hook, cache again
  • The next time you hit the cache, just use the existing instance

You will find that for a good understanding of the above process, and a good understanding of the Vue lifecycle, you can refer to cn.vuejs.org/v2/guide/in… Figure is as follows:

ComponentOptions, Key, Data, componentInstance, Tag, etc., and how to identify and use these attributes in Vue: How not to innovative new instance, how to trigger a new lifecycle hook activated deactivated, etc., if you are more interested in the logic of all details, you can refer to miss huang ustbhuangyi. Making. IO/vue – analysi…

Why

We can understand why Vue provides the built-in keep-alive component?

In the introduction, we also explained that in the real world, we still encounter many caching components, especially in routing scenarios.

One of the ideas of Vue is that it’s developer-friendly, the framework does a lot of things that allow developers to focus on their own logical development, which is one of the reasons why so many developers around the world love it. From this point on, it’s not hard to see why Vue has such a nice built-in component because there are so many requirements.

conclusion

As we can see, the implementation of keep-Alive components is not complicated, the entire file is only about 150 lines, but it is very powerful, all functions refer to cn.vuejs.org/v2/api/#kee… . So what can we learn from this component? What can we learn from this component?

Vue life cycle

Component definitions are limited, but they use most of the lifecycle hooks in Vue and are commonly used: created, Mounted, updated, and destroyed. This is also the cornerstone of writing good components, understanding and applying them properly, knowing their relationships and processes, and what is appropriate to do in the corresponding lifecycle.

As an added bonus, in the Created lifecycle hook function, we saw how to define non-responsive objects for instances. We can create this. XXX = XXXX directly in the created lifecycle hook function. Rather than the habit of putting these attributes in data(), which becomes a reactive object and adds extra overhead, this technique is worth learning and applying.

Vue handwritten render

Although very simple here, directly returned the default slot content of the node, but we can start from this point, in some special scenes, we still need to hand render, this part also need us to skilled use, detailed can refer to the official website cn.vuejs.org/v2/guide/re… About the use of the Render function and createElement, data objects.

Keys design

Keys is an array, and we know that keep-Alive also provides the ability to Max out a prop that specifies how many component instances can be cached. Once this number is reached, the cached component instances that have not been accessed for the longest time will be destroyed before new instances can be created. Note the key here: instances that have not been accessed for the longest time are destroyed.

How does this work? Sort it? Don’t bother, we know from the above analysis that Vue uses a very clever approach:

remove(keys, key)
keys.push(key)
Copy the code

In simple terms, delete the current key from the keys and push the key to the end of the array.

This ensures that the last element in the keys array is the freshest and the first element is the least fresh, which in our scenario corresponds to the most recently accessed instance.

// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
  pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
Copy the code

As you can see in the actual code, when Max is exceeded, the component instance corresponding to keys[0] is destroyed.

A memory leak

In destroyed, all instances are destroyed in order to release objects that are cached in the cache object. This is memory management to prevent memory leaks. This is the obvious memory destruction logic, as long as you pay attention to the problem will not encounter memory leaks.

But this is not absolute, let’s take a look at the latest one PR github.com/vuejs/vue/p… We found that we solved two issues of memory leakage. It seems intuitively that there should be no memory leakage.

This reminds us of memory leaks, which can be caused by code we accidentally write. We need to figure out how to use developer tools:

  • How do I know if THERE is a memory leak
  • To locate & resolve memory leaks

Other small Tips

  • Manually destroy the component instance, calling the instance’s$destroy()
  • Go to $slots.default to get the default children
  • Vnode has access to componentOptions and componentInstance, both of which are useful

The team number of Didi front-end technology team has been online, and we have synchronized certain recruitment information. We will continue to add more positions, and interested students can chat with us.