从源码看vue(v2.7.10)中的keep-alive的原理

本文详细分析了Vue内置组件keep-alive的工作原理,指出keep-alive只会缓存第一个组件,而非其内部的所有组件。通过源码解析,展示了keep-alive如何选择并缓存组件,以及在组件更新时如何利用缓存提高效率。同时,文章通过示例代码阐述了keep-alive在渲染和更新过程中的关键步骤,帮助读者深入理解其内部机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

能看这篇文章的我相信都用过keep-alive,keep-alive是vue的内置组件,主要功能是会缓存组件。但是缓存的只是第一个组件,如果在keep-alive内部放置多个组件是不会被缓存的。我们从源码来具体分析keep-alive的工作原理吧。

<template>
  <div>
    <keep-alive>
      <aa v-if="a%2"></aa>
      <bb v-else></bb>
    </keep-alive>
    <div @click="add">add</div>
  </div>
</template>

<script>
import aa from './aa.vue'
import bb from './bb.vue'
export default {
  name: "app",
  components: {
    aa,
    bb
  },
  data() {
    return {
      a: 1
    }
  },
  methods: {
    add() {
      this.a++
    }
  }
};
</script>
...
// aa.vue
<template>
  <div>
   我是aa
  </div>
</template>

<script>

export default {
  name: 'aa'
}
</script>
...
// bb.vue
<template>
  <div>
   我是bb
  </div>
</template>

<script>

export default {
  name: 'bb'
}
</script>

首先会执行app.vue的render方法生成vnode:
在这里插入图片描述
然后会调用该组件的data.hook.init(只有组件才有这个方法)方法生成app组件的vm实例:

init: function (vnode, hydrating) {
   if (vnode.componentInstance &&
        !vnode.componentInstance._isDestroyed &&
        vnode.data.keepAlive) {
        // kept-alive components, treat as a patch
        var mountedNode = vnode; // work around flow
        componentVNodeHooks.prepatch(mountedNode, mountedNode);
    }
    else {
        var child = (vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance));
        child.$mount(hydrating ? vnode.elm : undefined, hydrating);
    }
}

这里的child就是app.vue的vm实例,接下来执行vm实例上的$mount方法挂载组件。挂载的时候会执行组件的render方法。我们可以看到此时a为1所以会渲染aa组件。执行的顺序是从里到外,从前到后,从左到右。所以先执行_vm.a,在执行_c(“a”)生成vnode,然后执行_c(“keep-alive”)生成vnode,再依次往下执行_vm._v(" ")…,执行完后执行_c(“div”,[…])生成最终的vnode。

updateComponent = function () {
    vm._update(vm._render(), hydrating);
};
...
var render = function render() {
  var _vm = this,
    _c = _vm._self._c
  return _c(
    "div",
    [
      _c("keep-alive", [_vm.a % 2 ? _c("aa") : _c("bb")], 1),
      _vm._v(" "),
      _c("div", { on: { click: _vm.add } }, [_vm._v("add")]),
    ],
    1
  )
}

生成如下:
在这里插入图片描述
接下来执行app组件的_update方法,后执行createElm方法。由于第一个vnode是keep-alive,所以会生成keep-alive的vm实例:
在这里插入图片描述
可以看到aa组件被放入$slot当中。此时会继续去挂载keep-alive组件,然后去执行该组件的渲染方法:

render: function () {
   var slot = this.$slots.default;
   var vnode = getFirstComponentChild(slot);
   var componentOptions = vnode && vnode.componentOptions;
   if (componentOptions) {
       // check pattern
       var name_2 = _getComponentName(componentOptions);
       var _a = this, include = _a.include, exclude = _a.exclude;
       if (
       // not included
       (include && (!name_2 || !matches(include, name_2))) ||
           // excluded
           (exclude && name_2 && matches(exclude, name_2))) {
           return vnode;
       }
       var _b = this, cache = _b.cache, keys = _b.keys;
       var key = vnode.key == null
           ? // same constructor may get registered as different local components
               // so cid alone is not enough (#3269)
               componentOptions.Ctor.cid +
                   (componentOptions.tag ? "::".concat(componentOptions.tag) : '')
           : vnode.key;
       if (cache[key]) {
           vnode.componentInstance = cache[key].componentInstance;
           // make current key freshest
           remove$2(keys, key);
           keys.push(key);
       }
       else {
           // delay setting the cache until update
           this.vnodeToCache = vnode;
           this.keyToCache = key;
       }
       // @ts-expect-error can vnode.data can be undefined
       vnode.data.keepAlive = true;
   }
   return vnode || (slot && slot[0]);
}

可以看出会获取插槽里面的第一个vnode就是aa.vue,并且会判断有没有缓存,有的话会移除旧的位置并放入首位(LRU算法)。否则会设置为待缓存vnodeToCache。可以看出keep-alive的工作原理其实就是返回首位的vnode,放入超过一个的组件不会被显示。最后返回的vnode:
在这里插入图片描述
此时执行keep-alive组件的_update方法去把当前的vnode生成组件实例并挂载,挂载的最后结果就是将aa的template下的vnode转换成真实的dom。我们再看看什么时候去增加缓存的:

methods: {
   cacheVNode: function () {
       var _a = this, cache = _a.cache, keys = _a.keys, vnodeToCache = _a.vnodeToCache, keyToCache = _a.keyToCache;
       if (vnodeToCache) {
           var tag = vnodeToCache.tag, componentInstance = vnodeToCache.componentInstance, componentOptions = vnodeToCache.componentOptions;
           cache[keyToCache] = {
               name: _getComponentName(componentOptions),
               tag: tag,
               componentInstance: componentInstance
           };
           keys.push(keyToCache);
           // prune oldest entry
           if (this.max && keys.length > parseInt(this.max)) {
               pruneCacheEntry(cache, keys[0], keys, this._vnode);
           }
           this.vnodeToCache = null;
       }
   }
}

在组件被挂载之前会调用cacheVNode方法,可以看出取出了vnodeToCache的值,缓存了组件vm,并放入keys里面然后清空了vnodeToCache。当有缓存的时候会执行:

if (cache[key]) {
    vnode.componentInstance = cache[key].componentInstance;
    // make current key freshest
    remove$2(keys, key);
    keys.push(key);
}

从cache[key]中取出vm实例然后直接给当前的vnode,相当于省去了实例化的过程,然后放入首位。

总结

keep-alive组件就是取出内部第一个组件并返回,然后在挂载前放入缓存列表。当切换的时候如果有缓存的时候直接拿当前的实例覆盖新的vnode的实例,而不用重新生成vnode。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Young soul2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值