3
头图

Preface

Hello, everyone, I’m Lin Sanxin, uses the most easy-to-understand knowledge to explain the most difficult knowledge points is my motto, and is the prerequisite for advanced is my original intention.

In retrospect, when I first started writing, I wrote the Vue source code series , which are all included in my Nuggets column Vue source code analysis :

Today, I will tell you the basic principles keep-alive

Scenes

Maybe you will often encounter such a scenario in your usual development: there is a list page List.vue that can be filtered. When you click on an item, you enter the corresponding detail page. When you return to List.vue from the detail page, you find that the list page is refreshed. Up! The filter criteria just now are gone! ! !

截屏2021-12-19 上午11.08.50.png

keep-alive

What is it?

  • keep-alive is a Vue global component
  • keep-alive itself will not be rendered, nor will it appear in the parent component chain
  • keep-alive wrapping dynamic components, inactive components are cached instead of destroying them

how to use?

keep-alive receives three parameters:

  • include strings, regular expressions, and arrays can be passed, and the components whose names match successfully will be cached
  • exclude strings, regular expressions, and arrays can be passed. Components whose names match successfully will not be cached
  • max number can be passed to limit the maximum number of cache components

include and exclude , arrays of 161c9020bdbdfc

Dynamic component

<keep-alive :include="allowList" :exclude="noAllowList" :max="amount"> 
    <component :is="currentComponent"></component> 
</keep-alive>

Routing component

<keep-alive :include="allowList" :exclude="noAllowList" :max="amount">
    <router-view></router-view>
</keep-alive>

Source code

Component foundation

keep-alive earlier, 061c9020bdbe5d is a Vue global component, which receives three parameters:

  • include strings, regular expressions, and arrays can be passed, and components whose names match successfully will be cached
  • exclude strings, regular expressions, and arrays can be passed. Components whose names match successfully will not be cached
  • max numbers can be passed to limit the maximum number of cache components. If the number exceeds max it will be replaced LRU algorithm

By the way keep-alive in each life cycle:

  • created : Initialize a cache、keys , the former is used to store the virtual dom collection of the cache component, and the latter is used to store the key collection of the cache component
  • mounted : Real-time monitoring include、exclude these changes, and take appropriate action
  • destroyed : Delete all cache related things
keep-alive before, 061c9020bdbf5b will not be rendered on the page, so the attribute of abstract
// src/core/components/keep-alive.js

export default {
  name: 'keep-alive',
  abstract: true, // 判断此组件是否需要在渲染成真实DOM
  props: {
    include: patternTypes,
    exclude: patternTypes,
    max: [String, Number]
  },
  created() {
    this.cache = Object.create(null) // 创建对象来存储  缓存虚拟dom
    this.keys = [] // 创建数组来存储  缓存key
  },
  mounted() {
    // 实时监听include、exclude的变动
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  },
  destroyed() {
    for (const key in this.cache) { // 删除所有的缓存
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },
  render() {
      // 下面讲
  }
}

pruneCacheEntry function

In the life cycle destroyed we implemented above, we executed the operation of delete all caches, and this operation was implemented by calling pruneCacheEntry , let’s talk about what pruneCacheEntry

// src/core/components/keep-alive.js

function pruneCacheEntry (
  cache: VNodeCache,
  key: string,
  keys: Array<string>,
  current?: VNode
) {
  const cached = cache[key]
  if (cached && (!current || cached.tag !== current.tag)) {
    cached.componentInstance.$destroy() // 执行组件的destory钩子函数
  }
  cache[key] = null  // 设为null
  remove(keys, key) // 删除对应的元素
}

To sum up, three things have been done:

  • 1. Traverse the collection and execute the $destroy method of all cache components
  • 2. key the content of cache corresponding to null
  • 3. Delete the corresponding element in keys

render function

Hereafter include white list, exclude blacklist
render function mainly does these things:
  • Step 1: Get the first component of the keep-alive component name
  • Step Two: whether or not this component name if that can be whitelist, blacklist match, if can not be matched white list blacklist || be matched, the direct return VNode , does not execute down, if not met, Execute step 3 down
  • Step 3: Generate the cache key component ID and tag, and check whether the component has been cached in the cache collection. If it has been cached, take out the cache component directly and update the cache key in keys (this is LRU algorithm), if it has not been cached, continue to the fourth step of
  • Step 4: Save the component and its cache key cache、keys respectively, and check whether the number exceeds max , and delete it according to the LRU algorithm if the number exceeds 061c9020bdc128
  • Step 5: keepAlive property of this component instance to true. This is very important, as I will talk about below!
// src/core/components/keep-alive.js

render() {
  const slot = this.$slots.default
  const vnode: VNode = getFirstComponentChild(slot) // 找到第一个子组件对象
  const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
  if (componentOptions) { // 存在组件参数
    // check pattern
    const name: ?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
    const key: ?string = vnode.key == null // 定义组件的缓存key
      // 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]) { // 已经缓存过该组件
      vnode.componentInstance = cache[key].componentInstance
      // make current key freshest
      remove(keys, key)
      keys.push(key) // 调整key排序
    } else {
      cache[key] = vnode // 缓存组件对象
      keys.push(key)
      // prune oldest entry
      if (this.max && keys.length > parseInt(this.max)) { // 超过缓存数限制,将第一个删除
        pruneCacheEntry(cache, keys[0], keys, this._vnode)
      }
    }

    vnode.data.keepAlive = true // 渲染和执行被包裹组件的钩子函数需要用到
  }
  return vnode || (slot && slot[0])
}

Rendering

Let's first take a look at how a component of Vue is rendered. Let's start with render :

  • render : This function will convert the component to VNode
  • patch : This function will be directly rendered during the first rendering. According to the VNode , it will be directly rendered into real DOM. At the beginning of the second rendering, VNode will be compared with the old VNode and patched (the diff algorithm comparison occurs at this stage) , And then rendered into real DOM

截屏2021-12-19 下午8.45.25.png

keep-alive itself rendering

As I just said, keep-alive own components will not be rendered on the page. How is that done? In fact, by judging abstract on the component instance, if it is true , the instance is skipped, and the instance will not appear on the parent chain.

// src/core/instance/lifecycle.js

export function initLifecycle (vm: Component) {
  const options = vm.$options
  // 找到第一个非abstract的父组件实例
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }
  vm.$parent = parent
  // ...
}

Package component rendering

Let's talk about how the components wrapped keep-alive I just said that the VNode -> real DOM occurred in the patch stage, but in fact this is also to be subdivided: VNode -> instantiation -> _update -> real DOM, and the judgment of the component using the cache occurs in the instance In this stage, and this stage calls the createComponent , then let’s talk about this function:

// src/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 /* hydrating */)
    }

    if (isDef(vnode.componentInstance)) {
      initComponent(vnode, insertedVnodeQueue)
      insert(parentElm, vnode.elm, refElm) // 将缓存的DOM(vnode.elm)插入父元素中
      if (isTrue(isReactivated)) {
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
      }
      return true
    }
  }
}
  • When you first load the wrapped components, because keep-alive of render performed prior to assembly before the package is loaded, so at this time vnode.componentInstance value is undefined , and keepAlive is true , then the code went i(vnode, false /* hydrating */) not go down
  • When the package component is accessed again, vnode.componentInstance is the component instance that has been cached, then the insert(parentElm, vnode.elm, refElm) will be executed, so that the last DOM is directly inserted into the parent element.

Concluding remarks

I am Lin Sanxin, an enthusiastic front-end rookie programmer. If you are motivated, like the front-end, and want to learn the front-end, then we can make friends and fish together haha, fish school

image.png

refer to


Sunshine_Lin
2.1k 声望7.1k 粉丝