keep-alive如何保持组件状态(上)

简介: keep-alive的设计初衷:有些业务场景需要根据不同的判断条件,动态地在多个组件之间切换。频繁的组件切换会导致组件反复渲染,如果组件包含有大量的逻辑和dom节点,极易造成性能问题。其次,切换后组件的状态也会完全丢失。keep-alive的设计初衷就是为了保持组件的状态,避免组件的重复渲染。

keep-alive的设计初衷


有些业务场景需要根据不同的判断条件,动态地在多个组件之间切换。频繁的组件切换会导致组件反复渲染,如果组件包含有大量的逻辑和dom节点,极易造成性能问题。其次,切换后组件的状态也会完全丢失。keep-alive的设计初衷就是为了保持组件的状态,避免组件的重复渲染。


为什么keep-alive可以直接使用


开发者无需注册和引入,直接可以在模板中使用。 跟开发者使用Vue.component自定义的组件不同,keep-alive无需注册,在模板中直接可以使用,如下所示:


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


这是因为keep-alive是vue的内置组件,已经在vue中提前定义。


// core/components/keep-alive.js
export default {
  name: 'keep-alive',
  abstract: true,
  props: {
    include: patternTypes,
    exclude: patternTypes,
    max: [String, Number]
  },
  created () {
    this.cache = Object.create(null)
    this.keys = []
  },
  destroyed () {
    // keep-alive的销毁,将所有缓存的组件清除
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },
  mounted () {
    // 如果指定了include和exclude属性,需要实时观察当前这两个属性的变化,以及时的更新缓存
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  },
  render () {
    // keepAlive组件本身不会被渲染成dom节点,其render方法的处理逻辑的是将其包裹的组件的vnode返回
    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
        // 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
      // 1、如果缓存中存在该vnode,从缓存中取得该组件的实例(一个组件对应一颗vnode树,同时一个组件对应一个vue子类的实例),不再重新创建
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest
        // 将当前的组件的key作为最新的缓存(更新其在keys数组中的顺序)
        remove(keys, key)
        keys.push(key)
      } else {
        // 2、如果未命中缓存,添加到缓存
        cache[key] = vnode
        keys.push(key)
        // 如果缓存超过限制,淘汰最旧的缓存
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
      }
      // 标记为keepAlive组件
      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])
  }
}


// core/components/index.js
import KeepAlive from './keep-alive'
export default {
  KeepAlive
}


// core/global-api/index.js
// 导入内置组件
import builtInComponents from '../components/index'
/**
 * 为Vue添加全局方法和属性
 * @param {GlobalAPI} Vue 
 */
export function initGlobalAPI (Vue: GlobalAPI) {
  // ...省略了无关代码
  Vue.options = Object.create(null)
  // 添加内置组件keep-alive
  extend(Vue.options.components, builtInComponents)
}


buildInComponents中包含了keep-alive的定义。在initGlobalAPI方法中,将内置组件添加到了 vue的全局变量中。


extend(A, B)是个简单的对象属性复制方法。将对象B中的属性复制到对象A中。


keep-alive是如何保持组件状态的


为了保持组件状态,keep-alive设计了缓存机制。


我们知道,模板中的每个HTML标签在vue中由相应的vnode节点对象来表示。如果是HTML标签是组件标签,需要为该标签的vnode创建一个组件实例。组件实例负责组件内的HTML模板的编译和渲染。因此相比于普通HTML标签的vnode节点,组件vnode节点会存在componentOptionscomponentInstance 这两个属性中保存组件选项对象和组件实例的引用。


首先,我们从keep-alive组件的实现代码中可以看到在组件的created钩子中设计了缓存机制:


created () {
    this.cache = Object.create(null)
    this.keys = []
}


keep-alive设置了cachekeys两个属性来缓存子组件。其中cache中的每项是一个以所包裹的组件的组件名为key,包裹组件对应的vnoded为值的对象。keys的每一项是其所包裹的组件的组件名。


render 函数是vue实例和vue组件实例中用来创建vnode的方法。我们在实际的应用中,一般是通过template和el来指定模板,然后由vue将模板编译成render函数。如果用户希望能更灵活地控制vnode的创建可以提供自定义的render函数。



相关文章
|
6月前
|
存储 缓存 JavaScript
keep-alive的两个属性的作用
keep-alive的两个属性的作用
141 0
|
6月前
|
存储 缓存 JavaScript
缓存组件状态,提升用户体验:探索 keep-alive 的神奇世界
缓存组件状态,提升用户体验:探索 keep-alive 的神奇世界
缓存组件状态,提升用户体验:探索 keep-alive 的神奇世界
|
6月前
|
存储 缓存 JavaScript
Uservue 中 keep-alive 组件的作用
Uservue 中 keep-alive 组件的作用
66 0
|
24天前
|
缓存 UED
动态组件与 keep-alive 搭配使用时的生命周期钩子
【10月更文挑战第19天】动态组件与 keep-alive 搭配使用时的生命周期钩子为我们提供了更多的灵活性和可操作性,使我们能够更好地管理组件的状态和行为。深入理解和掌握这些钩子的特点和用法,以便在实际开发中能够更加得心应手地运用它们,为我们的应用带来更优秀的用户体验和性能表现。
111 62
|
1月前
|
缓存 JavaScript UED
|
3月前
|
前端开发
React组件实例更改state状态值(四)
【8月更文挑战第14天】React组件实例更改state状态值(四)
38 1
React组件实例更改state状态值(四)
|
3月前
|
前端开发 JavaScript 算法
React Server Component 使用问题之想在路由切换时保持客户端状态,如何实现
React Server Component 使用问题之想在路由切换时保持客户端状态,如何实现
|
5月前
|
缓存 监控 JavaScript
使用 keep-alive 时,监控和分析组件的缓存行为
使用 keep-alive 时,监控和分析组件的缓存行为
|
5月前
|
缓存
keep-alive 组件有哪些常用的属性和配置选项
keep-alive 组件有哪些常用的属性和配置选项