1. 程式人生 > >組件的 keep-alive 簡介

組件的 keep-alive 簡介

兩個 對象 hydra gist dom pat 會同 代碼 處理

本篇文章,我們來講一下keep-alive的實現。

Vue中,有三個內置的抽象組件,分別是keep-alivetransitiontransition-group

它們都有一個共同的特點,就是自身不會渲染一個DOM元素,也不會出現在父組件鏈中。

keep-alive的作用,是包裹動態組件時,會緩存不活動的組件實例,而不是銷毀它們。具體的用法見這裏。

該組件的定義,是在src/core/components/keep-alive.js文件中。

它會在Vue初始化時,添加在Vue.options.components上,所以在所有的組件中,都可以直接只用它

直接看代碼:

export default {
  name: ‘keep-alive‘,
  abstract: true,

  props: {
    ...
  },

  created () {
    this.cache = Object.create(null)
  },

  destroyed () {
   ...
  },

  watch: {
    ...
  },

  render () {
    ...
  }
}

name不用多說,abstract: true 這個條件我們自己定義組件時通常不會用,

它是用來標識當前的組件是一個抽象組件,它自身不會渲染一個真實的DOM元素。

比如在創建兩個vm實例之間的父子關系時,會跳過抽象組件的實例:

  let parent = options.parent
if (parent && !options.abstract) { while (parent.$options.abstract && parent.$parent) { parent = parent.$parent } parent.$children.push(vm) }

props表示我們可以傳入include來匹配哪些組件可以緩存exclude來匹配哪些組件不緩存。

created鉤子函數調用時,會創建一個this.cache對象用於緩存它的子組件。

destroyed表示keep-alive被銷毀時,會同時銷毀它緩存的組件,並調用deactivated鉤子函數。

function pruneCacheEntry (vnode: ?VNode) {
  if (vnode) {
    if (!vnode.componentInstance._inactive) {
      callHook(vnode.componentInstance, ‘deactivated‘)
    }
    vnode.componentInstance.$destroy()
  }
}



watch是在我們改變props傳入的值時,同時對this.cache緩存中的數據進行處理。

function pruneCache (cache: VNodeCache, filter: Function) {
  for (const key in cache) {
    const cachedNode: ?VNode = cache[key]
    if (cachedNode) {
      const name: ?string = getComponentName(cachedNode.componentOptions)
      if (name && !filter(name)) {
        pruneCacheEntry(cachedNode)
        cache[key] = null
      }
    }
  }
}


抽象組件沒有實際的DOM元素,所以也就沒有template模板,它會有一個render函數,我們就來看看裏面進行了哪些操作。

  render () {
    const vnode: VNode = getFirstComponentChild(this.$slots.default)
    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
      // check pattern
      const name: ?string = getComponentName(componentOptions)
      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
      if (this.cache[key]) {
        vnode.componentInstance = this.cache[key].componentInstance
      } else {
        this.cache[key] = vnode
      }
      vnode.data.keepAlive = true
    }
    return vnode
  }


首先,調用getFirstComponentChild方法,來獲取this.$slots.default中的第一個元素。

export function getFirstComponentChild (children: ?Array<VNode>): ?VNode {
  return children && children.filter((c: VNode) => c && c.componentOptions)[0]
}

this.$slots.default中包含的是什麽內容,我們在《slot和作用域插槽》中已經詳細的做了講解。

從上面的方法我們可以看到,在我們會過濾掉非自定義的標簽,然後獲取第一個自定義標簽所對應的vnode

所以,如果keep-alive裏面包裹的是html標簽,是不會渲染的。

然後獲取componentOptions

vdom——VNode中我們介紹過componentOptions包含五個元素

{ Ctor, propsData, listeners, tag, children }

function getComponentName (opts: ?VNodeComponentOptions): ?string {
  return opts && (opts.Ctor.options.name || opts.tag)
}

通過getComponentName方法來獲取組件名,然後判斷該組件是否合法,

如果include不匹配或exclude匹配,則說明該組件不需要緩存,

此時直接返回該vnode

否則,vnode.key不存在則生成一個,存在則就用vnode.key作為key

然後把該vnode添加到this.cache中,並設置vnode.data.keepAlive = true

最終返回該vnode

以上只是render函數執行的過程,keep-alive本身也是一個組件

render函數調用生成vnode後,同樣會走__patch__。在創建和diff的過程中,

也會調用initprepatchinsertdestroy鉤子函數。

不過,每個鉤子函數中所做的處理,和普通組件有所不同。

  init (
    vnode: VNodeWithData,
    hydrating: boolean,
    parentElm: ?Node,
    refElm: ?Node
  ): ?boolean {
if (!vnode.componentInstance || vnode.componentInstance._isDestroyed) { const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance, parentElm, refElm ) child.$mount(hydrating ? vnode.elm : undefined, hydrating)
} else if (vnode.data.keepAlive) { // kept-alive components, treat as a patch const mountedNode: any = vnode // work around flow componentVNodeHooks.prepatch(mountedNode, mountedNode) } },

keep-alive組件內調用__patch__時,如果render返回的vnode是第一次使用,

則走正常的創建流程,如果之前創建過且添加了vnode.data.keepAlive

則直接調用prepatch方法,且傳入的新舊vnode相同。

  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    const options = vnode.componentOptions
    const child = vnode.componentInstance = oldVnode.componentInstance
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children
    )
  },

prepatch函數做了哪些工作,之前也詳細的介紹過,這裏就不多說了。

簡單的總結,就是依據新vnode中的數據,更新組件內容

  insert (vnode: MountedComponentVNode) {
    if (!vnode.componentInstance._isMounted) {
      vnode.componentInstance._isMounted = true
      callHook(vnode.componentInstance, ‘mounted‘)
    }
    if (vnode.data.keepAlive) {
      activateChildComponent(vnode.componentInstance, true /* direct */)
    }
  },

在組件插入到頁面後,如果是vnode.data.keepAlive則會調用activateChildComponent

這裏面主要是調用子組件的activated鉤子函數,並設置vm._inactive的標識狀態。

  destroy (vnode: MountedComponentVNode) {
    if (!vnode.componentInstance._isDestroyed) {
      if (!vnode.data.keepAlive) {
        vnode.componentInstance.$destroy()
      } else {
        deactivateChildComponent(vnode.componentInstance, true /* direct */)
      }
    }
  }

在組件銷毀時,如果是vnode.data.keepAlive返回true

則只調用deactivateChildComponent,這裏面主要是調用子組件的deactivated鉤子函數,

並設置vm._directInactive的標識狀態。因為vnode.data.keepAlivetrue的組件,

是會被keep-alive緩存起來的,所以不會直接調用它的$destroy()方法,

上面我們也提到了,當keep-alive組件被銷毀時,會觸發它緩存中所有組件的$destroy()

因為keep-alive包裹的組件狀態變化,還會觸發其子組件的activateddeactivated鉤子函數,

activateChildComponentdeactivateChildComponent也會做一些這方面的處理,細節大家可以自行查看。

組件的 keep-alive 簡介