重学Vue组件keep-alive

重学Vue组件<keep-alive>

一、学习目标


  • keep-alive是怎样实现缓存的
  • 简单了解 activeed , deactived 钩子函数

二、概念


直译:“存活”、 “缓存” 、“保持状态” 、“持久化”
官方:使用 keep-alive 包裹动态组件时,会 缓存 不活动的组件实例,而不是销毁它们

<!-- 基本 -->
<keep-alive include="test">
  <test></test>
  <demo></demo>
  <comp1></comp1>
</keep-alive>

属性:

  • include - 字符串或正则表达式或数组。只有名称匹配的组件会被缓存。
  • exclude - 字符串或正则表达式或数组。任何名称匹配的组件都不会被缓存。
  • max - 数字。最多可以缓存多少组件实例。

实例:

<!-- 逗号分隔字符串 -->
<keep-alive include="a,b">
  <component :is="view"></component>
</keep-alive>

<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
  <component :is="view"></component>
</keep-alive>

<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
  <component :is="view"></component>
</keep-alive>

<!-- exclude a组件不会被缓存 -->
<keep-alive exclude="a">
  <component :is="view"></component>
</keep-alive>

<!-- 最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉 -->
<keep-alive :max="10">
  <component :is="view"></component>
</keep-alive>

三、项目中使用场景之一


目的:主要用于 保留组件状态避免重新渲染

<div class="app-content-wrapper comp-common-apply-detail">
    <el-tabs v-model="componentName" v-tabs-permission="tabList">
      <el-tab-pane
        v-for="item in tabsListRequired"
        :key="item.permissionId"
        :label="item.tabName"
        :name="item.compName"
      />
    </el-tabs>
    <div class="app-content-body">
      <transition name="fade" mode="out-in">
        <keep-alive include="CompBaseInfo,CompApproveFlowDetail,CompAttachments,CompFinancingPlanInfo">
          <component :is="componentName"/>
        </keep-alive>
      </transition>
    </div>
  </div>

四、实现原理


官方:一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中
源码 ( https://github.com/vuejs/vue/blob/dev/src/core/components/keep-alive.js【Show Me Code】

  • 1、证明是一个组件
  • 2、抽象组件?
  • 3、为什么我们能传 include、exclude、max,以及这些属性不同数据类型的兼容性处理?
  • 4、如何缓存?
  • 5、缓存的是什么?

内置组件

const patternTypes: Array<Function> = [String, RegExp, Array]

export default {
  name: 'keep-alive',
  abstract: true,//标识是一个抽象组件。它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中(感兴趣的可以深挖)

  props: {
    include: patternTypes,
    exclude: patternTypes,
    max: [String, Number]
  },
  ...
  methods:{},
}

//匹配组件名称
//@param pattern 匹配对象(include、exlude)
//@param name  组件名称
function matches (pattern: string | RegExp | Array<string>, name: string): boolean {
  if (Array.isArray(pattern)) { //数组
    return pattern.indexOf(name) > -1
  } else if (typeof pattern === 'string') { //字符串
    return pattern.split(',').indexOf(name) > -1
  } else if (isRegExp(pattern)) { //正则
    return pattern.test(name)
  }
  /* istanbul ignore next */
  return false
}

建一个“存储池”

created () {
    this.cache = Object.create(null) //创建缓存对象 this.cache = { 'key1':'组件1', 'key2':'组件2' }
    this.keys = []  //创建保存组件key的数组,key就是this.cache中的键值
  }

如何进行缓存 -render()

  • 1.获取第一个子组件的节点
const slot = this.$solts.default
const vnode = getFirstComponentChild(solt)
// 只处理第一个子元素,所以一般和它搭配使用的有 component 动态组件或者是 router-view
  • 2.获取该组件的名称
/* 获取该组件节点的名称 */
const name = getComponentName(componentOptions)

/* 优先获取组件的name字段,如果name不存在则获取组件的tag */
function getComponentName (opts: ?VNodeComponentOptions): ?string {
  return opts && (opts.Ctor.options.name || opts.tag)
}
  • 3.拿到的组件名称和include、exclude中的值做match
  // 如果该组件不做缓存-则返回该组件 (vnode)
  const { include, exclude } = this
  if (
    // not included
    (include && (!name || !matches(include, name))) ||
    // excluded
    (exclude && name && matches(exclude, name))
  ) {
    return vnode
  }
  // 否则-缓存下来
  const { cache, keys } = this
    // 获取组件的key值
  const key = vnode.key == null?
  componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
  : vnode.key

    //拿到key值后取this.cach对象中寻找是否有该值,如果有,说明组件有缓存,命中缓存
    // 如果命中缓存,则直接从缓存中拿 vnode 的组件实例 //
  if (cache[key]) {
      vnode.componentInstance = cache[key].componentInstance
      /* 调整该组件key的顺序,将其从原来的地方删掉并重新放在最后一个 */  
      // LRU 缓存机制(Least recently used,最近最少使用)
      remove(keys, key) //删除你
      keys.push(key) //再加你-把你放在心底
  } else {
      // 如果没有,则将组件存入缓存
      this.vnodeToCache = vnode
      this.keyToCache = key
      // cacheVNode()
  }

  cacheVNode() {
    const { cache, keys, vnodeToCache, keyToCache } = this
    if (vnodeToCache) { //被缓存的组件vnode
      const { tag, componentInstance, componentOptions } = vnodeToCache
      cache[keyToCache] = {
        name: getComponentName(componentOptions),
        tag,
        componentInstance,
      }
      keys.push(keyToCache)

      // 移除最旧的vnode
      if (this.max && keys.length > parseInt(this.max)) {
        pruneCacheEntry(cache, keys[0], keys, this._vnode)
      }
      this.vnodeToCache = null
    }
  }

生命周期 (src/core/instance/lifecycle.js)

  • 我们都知道组件一旦被keep-alive缓存,那么再次渲染的时候就不会执行 created、mounted 等钩子函数
  • 当组件在 keep-alive 内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行
  • 它的执行时机是 keep-alive 包裹的组件渲染的时候
  • 在渲染的最后一步,会执行 invokeInsertHook() 函数执行 vnode 的 insert() 钩子函数,insert() 函数主要逻辑是执行 activateChildComponent() 函数.
  • 执行组件的 acitvated 钩子函数,并且递归去执行它的所有子组件的 activated 钩子函数
export function activateChildComponent (vm: Component, direct?: boolean) {
  ...
  if (vm._inactive || vm._inactive === null) {
    vm._inactive = false
    for (let i = 0; i < vm.$children.length; i++) {
      activateChildComponent(vm.$children[i])
    }
    callHook(vm, 'activated')
  }
}
  • 同样在组件销毁时,执行 destroy()函数中的 deactivateChildComponent()
  • 执行组件的 deacitvated 钩子函数,并且递归去执行它的所有子组件的 deactivated 钩子函数
  export function deactivateChildComponent (vm: Component, direct?: boolean) {
  ...
  if (!vm._inactive) {
    vm._inactive = true
    for (let i = 0; i < vm.$children.length; i++) {
      deactivateChildComponent(vm.$children[i])
    }
    callHook(vm, 'deactivated')
  }
}

五、总结


  • keep-alive 是vue的一个内置组件,主要是用来控制组件的缓存,它可以设置三个参数,来设置哪个组件需要被缓存,或者不被缓存,同时还有缓存阙值
  • 缓存的是组件的vnode,都不需要再次创建Vue实例,而是用之前缓存的实例
  • keep-alive的缓存策略使用的是LRU缓存淘汰策略

六、扩展


  • 1、抽象组件为什么不出现在组件的父组件链中
// initLifecycle
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
  while (parent.$options.abstract && parent.$parent) {
    parent = parent.$parent
  }
  parent.$children.push(vm)
}
vm.$parent = parent
  • 2、LRU 缓存机制-(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”
  • 3、虚拟DOM (VNode):在Vue中,VNode对象来描述真实dom节点,对象包括标签名、数据、子节点、键值等一些属性
  • 4、keep-alive组件渲染过程,初次渲染和缓存渲染有什么不同?

收工!

感谢!!

再见!!!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值