Talk about the use of the keep-alive component and its implementation principle

write in front

Because I am very interested in Vue.js, and the technology stack I usually work on is also Vue.js, I spent some time studying and studying the Vue.js source code in the past few months, and made a summary and output.

The original address of the article: https://github.com/answershuto/learnVue .

During the learning process, Chinese annotations were added to Vue https://github.com/answershuto/learnVue/tree/master/vue-src and Vuex annotations https://github.com/answershuto/learnVue/tree/ master/vuex-src , I hope it can be helpful to other friends who want to learn the source code.

There may be some deviations in understanding. You are welcome to raise issues and point out, learn together and make progress together.

keep-alive

keep-alive is a built-in component of Vue.js. It is able to keep inactive component instances in memory instead of destroying them directly, it is an abstract component that will not be rendered into the real DOM and will not appear in the parent component chain.

It provides include and exclude properties to allow components to conditionally cache.

For details, please refer to the official website .

use

usage

<keep-alive>
    <component></component>
</keep-alive>

The component components here will be cached.

take a chestnut

<keep-alive>
    <coma v-if="test"></coma>
    <comb v-else="test"></comb>
</keep-alive>
<button @click="test=handleClick">请点击</button>
export default {
    data () {
        return {
            test: true
        }
    },
    methods: {
        handleClick () {
            this.test = !this.test;
        }
    }
}

When the button is clicked, the two components coma and comb will switch, but the state of these two components will be cached at this time. For example, there is an input tag in the coma and comb components, so the content in the input tag will not be Disappeared due to component switching.

props

The keep-alive component provides include and exclude properties to allow the component to conditionally cache, both of which can be represented by a comma-separated string, a regular expression, or an array.

<keep-alive include="a">
  <component></component>
</keep-alive>

The component named a will be cached.

<keep-alive exclude="a">
  <component></component>
</keep-alive>

A component with name a will not be cached.

life hook

keep-alive provides two life hooks, activated and deactivated.

Because keep-alive will save the component in memory and will not destroy and recreate it, it will not call the created method of the component again. You need to use the two life hooks activated and deactivated to know whether the current component is active.


In-depth keep-alive component implementation

After talking about the use of the keep-alive component, let's take a look at how the keep-alive component implements component caching from the perspective of source code?

created and destroyed hooks

The created hook will create a cache object, which is used as a cache container to store vnode nodes.

created () {
    /* 缓存对象 */
    this.cache = Object.create(null)
},

The destroyed hook clears all component instances in the cache when the component is destroyed.

/* destroyed钩子中销毁所有cache中的组件实例 */
destroyed () {
    for (const key in this.cache) {
        pruneCacheEntry(this.cache[key])
    }
},

render

Next is the render function.

render () {
    /* 得到slot插槽中的第一个组件 */
    const vnode: VNode = getFirstComponentChild(this.$slots.default)

    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
        // check pattern
        /* 获取组件名称,优先获取组件的name字段,否则是组件的tag */
        const name: ?string = getComponentName(componentOptions)
        /* name不在inlcude中或者在exlude中则直接返回vnode(没有取缓存) */
        if (name && (
        (this.include && !matches(this.include, name)) ||
        (this.exclude && matches(this.exclude, name))
        )) {
            return vnode
        }
        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
        /* 如果已经做过缓存了则直接从缓存中获取组件实例给vnode,还未缓存过则进行缓存 */
        if (this.cache[key]) {
            vnode.componentInstance = this.cache[key].componentInstance
        } else {
            this.cache[key] = vnode
        }
        /* keepAlive标记位 */
        vnode.data.keepAlive = true
    }
    return vnode
}

First, get the first child component through getFirstComponentChild, and get the name of the component (if there is a component name, the component name is used directly, otherwise the tag will be used). Next, the name will be matched with the include and exclude attributes. If the match is unsuccessful (indicating that no caching is required), the vnode will be returned directly without any operation. vnode is a VNode type object. Students who do not know VNode can refer to the author's Another article "VNode Node" .

/* 检测name是否匹配 */
function matches (pattern: string | RegExp, name: string): boolean {
  if (typeof pattern === 'string') {
    /* 字符串情况,如a,b,c */
    return pattern.split(',').indexOf(name) > -1
  } else if (isRegExp(pattern)) {
    /* 正则 */
    return pattern.test(name)
  }
  /* istanbul ignore next */
  return false
}

The function to detect the match between include and exclude attributes is very simple. The include and exclude attributes support strings such as "a, b, c" where the component names are separated by commas and regular expressions. matches detect whether the current component is matched in these two ways.

if (this.cache[key]) {
    vnode.componentInstance = this.cache[key].componentInstance
} else {
    this.cache[key] = vnode
}

The next thing is very simple. Search in this.cache according to the key. If it exists, it means that it has been cached before, and directly overwrite the componentInstance (component instance) of the cached vnode to the current vnode. Otherwise store the vnode in the cache.

Finally, return to the vnode (when there is a cache, the componentInstance of the vnode has been replaced with the one in the cache).

watch

Use watch to monitor the changes of the two properties of pruneCache and pruneCache, and modify the cached data in the cache cache when they change.

watch: {
    /* 监视include以及exclude,在被修改的时候对cache进行修正 */
    include (val: string | RegExp) {
        pruneCache(this.cache, this._vnode, name => matches(val, name))
    },
    exclude (val: string | RegExp) {
        pruneCache(this.cache, this._vnode, name => !matches(val, name))
    }
},

Let's take a look at the implementation of pruneCache.

/* 修正cache */
function pruneCache (cache: VNodeCache, current: VNode, filter: Function) {
  for (const key in cache) {
    /* 取出cache中的vnode */
    const cachedNode: ?VNode = cache[key]
    if (cachedNode) {
      const name: ?string = getComponentName(cachedNode.componentOptions)
      /* name不符合filter条件的,同时不是目前渲染的vnode时,销毁vnode对应的组件实例(Vue实例),并从cache中移除 */
      if (name && !filter(name)) {
        if (cachedNode !== current) {
          pruneCacheEntry(cachedNode)
        }
        cache[key] = null
      }
    }
  }
} 

/* 销毁vnode对应的组件实例(Vue实例) */
function pruneCacheEntry (vnode: ?VNode) {
  if (vnode) {
    vnode.componentInstance.$destroy()
  }
}

Traverse all items in the cache, if they do not meet the rules specified by the filter, pruneCacheEntry will be executed. pruneCacheEntry will call the $destroy method of the component instance to destroy the component.

finally

Vue.js abstracts DOM nodes into VNode nodes one by one , and the cache of keep-alive components is also based on VNode nodes instead of directly storing the DOM structure. It caches the components that meet the conditions (pruneCache and pruneCache) in the cache object, and then takes the vnode node out of the cache object and renders it when it needs to be re-rendered.

about

Author: Ran Mo

Email:[email protected] or [email protected]

Github: https://github.com/answershuto

Zhihu: https://www.zhihu.com/people/cao-yang-49/activities

Please indicate the source when reprinting, thank you.

Welcome to pay attention to my public number

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324163813&siteId=291194637