学习一下vue2 keep-alive

154 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 30 天,点击查看活动详情

start

  • 一直对 keep-alive 掌握的不熟练,今天学习一下

开始

官方解释:

包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

基础使用

app.vue

<template>
  <!-- keep-alive 的基础用法 -->
  <div id="app">
    <!-- <keep-alive>
      <component :is="view"></component>
    </keep-alive> -->
​
    
    <keep-alive>
      <HelloWorld v-if="view === 'HelloWorld'"> </HelloWorld>
      <demo v-else />
    </keep-alive>
​
    <button @click="checkout">点我</button>
  </div>
</template><script>
import HelloWorld from '@/components/HelloWorld.vue'
import demo from '@/components/demo.vue'export default {
  name: 'app',
  data() {
    return {
      view: 'HelloWorld',
    }
  },
  components: {
    HelloWorld,
    demo,
  },
  methods: {
    checkout() {
      if (this.view === 'demo') {
        this.view = 'HelloWorld'
      } else {
        this.view = 'demo'
      }
    },
  },
}
</script>

HelloWorld.vue

<template>
  <div>我是helloworld</div>
</template><script>
export default {
  created() {
    // 首次加载的时候,会执行。但是因为被缓存了,后续切换组件不会执行;
    console.log('helloworld组件---created')
  },
  activated() {
    console.log('helloworld组件---activated')
  },
  deactivated() {
    console.log('helloworld组件---deactivated')
  },
}
</script><style></style>

demo.vue

<template>
  <div>我是demo函数</div>
</template><script>
export default {
  created() {
    // 首次加载的时候,会执行。但是因为被缓存了,后续切换组件不会执行;
    console.log('demo组件---created')
  },
  activated() {
    console.log('demo组件---activated')
  },
  deactivated() {
    console.log('demo组件---deactivated')
  },
}
</script>

生命周期函数

代码:

<template>
  <div>我是demo函数</div>
</template><script>
export default {
  created() {
    // 首次加载的时候,会执行。但是因为被缓存了,后续切换组件不会执行;
    console.log('demo组件---created')
  },
  activated() {
    console.log('demo组件---activated')
  },
  deactivated() {
    console.log('demo组件---deactivated')
  },
}
</script>

效果:

20220828164140.gif

实践结果:

  • 首次加载某个组件的时候,会执行该组件的 created 钩子,后续切换组件,不会触发组件的 created ;
  • 使用 keep-alive 缓存的组件,来回切换的时候,激活的时候会触发 activated,取消激活的时候会触发deactivated
  • activate 英文释义:激活的。

其他参数

include and exclude

2.1.0 新增

includeexclude prop 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:

<!-- 逗号分隔字符串 -->
<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>

max

2.5.0 新增

最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。

<keep-alive :max="10">
  <component :is="view"></component>
</keep-alive>

tips:

<keep-alive :include="['HelloWorld']">
   <router-view></router-view>
</keep-alive>
​
​
​
//  HelloWorld.vue
<template>
  <div>我是helloworld</div>
</template><script>
export default {
  name: 'HelloWorld', // 这里需要声明
  created() {
    // 首次加载的时候,会执行。但是因为被缓存了,后续切换组件不会执行;
    console.log('helloworld组件---created')
  },
  activated() {
    console.log('helloworld组件---activated')
  },
  deactivated() {
    console.log('helloworld组件---deactivated')
  },
}
</script>

// 使用路由懒加载的情况,页面需要添加 name 属性

总结

  • keep-alive 主要作用:缓存组件;
  • 支持参数: include, exclude,max
  • 支持生命周期钩子: activated,取消激活的时候会触发deactivated

阅读源码

"vue": "^2.5.22"

node_modules\vue\src\core\components\keep-alive.js
/* @flow */import { isRegExp, remove } from 'shared/util'
import { getFirstComponentChild } from 'core/vdom/helpers/index'type VNodeCache = { [key: string]: ?VNode };
​
// 获得组件的名称
function getComponentName (opts: ?VNodeComponentOptions): ?string {
  return opts && (opts.Ctor.options.name || opts.tag)
}
​
// 解析 include/exclude 的函数,包含则返回 true
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
}
​
// 减少缓存 (判断需不需要删除缓存的方法)
function pruneCache (keepAliveInstance: any, filter: Function) {
  // keepAliveInstance => this  => vm实例
  const { cache, keys, _vnode } = keepAliveInstance
  for (const key in cache) {
    const cachedNode: ?VNode = cache[key]
​
    // 如果缓存中存在 该虚拟节点
    if (cachedNode) {
​
      // 获取组件的名称
      const name: ?string = getComponentName(cachedNode.componentOptions)
​
      // 存在名字,且 过滤函数为 false,删除该缓存 (例如 include不包含返回false,不包含则删除缓存)
      if (name && !filter(name)) {
        pruneCacheEntry(cache, key, keys, _vnode)
      }
    }
  }
}
​
// 减少缓存入口 (真正去删除缓存的方法)
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()
  }
  cache[key] = null
  remove(keys, key)
}
​
// 2.1 数组正则字符串
const patternTypes: Array<Function> = [String, RegExp, Array]
​
export default {
  // 1. 组件名
  name: 'keep-alive',
​
  // 1.1 是否是虚拟组件  // 判断此组件是否需要在渲染成真实DOM
  abstract: true,
  
  // 2. 组件属性
  props: {
    include: patternTypes,
    exclude: patternTypes,
    max: [String, Number]
  },
​
  created () {
    // 3. 创建了一个空对象用来存储缓存的虚拟dom
    this.cache = Object.create(null)
    //  3.1 创建一个数组 存储key
    this.keys = []
  },
​
  destroyed () {
    // 5. 组件销毁的时候
    for (const key in this.cache) {
      // 5.1 直接调用删除组件的方式
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },
​
  mounted () {
    // 4. 监听 include exclude。
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  },
​
  render () {
    // 获取默认插槽
    const slot = this.$slots.default
​
    // 得到插槽中第一个组件的vnode
    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
        (include && (!name || !matches(include, name))) ||
        // excluded
        // 名字在 exclude
        (exclude && name && matches(exclude, name))
      ) {
        // 直接返回虚拟节点
        return vnode
      }
​
      const { cache, keys } = this
​
      // 定义组件的key
      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
​
        // 如果存在缓存
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest
        // 保证当前key是最新的,删除历史的key,讲key放在数组最末尾
        remove(keys, key)
        keys.push(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])
  }
}
​
// shared/util  
export function remove (arr: Array<any>, item: any): Array<any> | void {
  if (arr.length) {
    const index = arr.indexOf(item)
    if (index > -1) {
      return arr.splice(index, 1)
    }
  }
}

总结:

  • 跟着注释的序号,依次进行阅读
  • keep-alive本质是一个组件;
  • 组件内部缓存着 vnode。
  • 自定义了一个render函数,结合插槽和缓存的 vnode完成了组件的缓存。
  • 传入的参数 includeexclude,max,都是在render中使用的。