keep-alive 解決vue單頁面應用tab頁切換緩存問題

需求:

基於vue 2.6版本開發的功能複雜的單頁面應用,產品提出的需求是像瀏覽器tab頁同樣,從菜單打開的若干個頁面以前能夠切換,且切換到其餘頁面時能保留其餘頁面正在編輯的數據。同時,關閉tab頁以後,數據就不保留了。javascript

現狀:

cn.vuejs.org/v2/api/#kee…vue

  • <keep-alive> 主要用於保留組件狀態或避免從新渲染,包裹動態組件時,會緩存不活動的組件實例,而不是銷燬它們。
  • includeexclude 屬性容許組件有條件地緩存。兩者均可以用逗號分隔字符串、正則表達式或一個數組來表示:
  • 當組件在 <keep-alive> 內被切換,它的 activateddeactivated 這兩個生命週期鉤子函數將會被對應執行。 在 2.2.0 及其更高版本中,activateddeactivated 將會在 <keep-alive> 樹內的全部嵌套組件中觸發。
  • <transition> 類似,<keep-alive> 是一個抽象組件:它自身不會渲染一個 DOM 元素,也不會出如今父組件鏈中。
  • 若是有多個條件性的子元素,<keep-alive> 要求同時只有一個子元素被渲染。

----------------> 爲何解決不了問題?<------------------java

<keep-alive :include="keepaliveList">
	<router-view :key="$route.fullPath"/> </keep-alive> 複製代碼

源碼改動:

github.com/RobbinBaauw…node

// 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)
}

function matches (pattern: string | RegExp | Array<string>, key: ?string, name: string): boolean {
  function matchesValue(value: string) {
    if (Array.isArray(pattern)) {
      return pattern.indexOf(value) > -1
    } else if (typeof pattern === 'string') {
      return pattern.split(',').indexOf(value) > -1
    } else if (isRegExp(pattern)) {
      return pattern.test(value)
    }
    /* istanbul ignore next */
    return false
  }
    // 相比於原來的代碼主要差異是在這邊對key 和name 都進行了匹配
  return (key && matchesValue(key)) || matchesValue(name);
}

function pruneCache (keepAliveInstance: any, filter: Function) {
    // keepAliveInstance:當前keep-alive組件
    // cache:全部緩存的key-組件vnode鍵值對
    // keys:全部緩存的key
    // _vnode:當前激活的組件vnode
  const { cache, keys, _vnode } = keepAliveInstance
  for (const key in cache) {
    const cachedNode: ?VNode = cache[key]
    if (cachedNode) {
        // 路由組件必需要有name
      const name: ?string = getComponentName(cachedNode.componentOptions)
      if (name && !filter(key, 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)
}

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

export default {
  name: 'keep-alive',
    // 抽象組件,還有如transition, slot等
  abstract: true,

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

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

  destroyed () {
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },

  mounted () {
      // this指的是當前keep-alive組件
    this.$watch('include', val => {
      pruneCache(this, (key, name) => matches(val, key, name))
    })
    this.$watch('exclude', val => {
      pruneCache(this, (key, name) => !matches(val, key, name))
    })
  },

  render () {
      // 子節點數組
    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 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

      const { include, exclude } = this
      if (
        // not included
        (include && (!name || !matches(include, key, name))) ||
        // excluded
        (exclude && name && matches(exclude, key, name))
      ) {
          // include(包含的組件緩存生效) 與 exclude(排除的組件不緩存,優先級大於include)
        return vnode
      }

      const { cache, keys } = this

      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest
        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])
  }
}
複製代碼

踩坑:

  1. componnet name

匹配首先檢查組件自身的 name 選項,若是 name 選項不可用,則匹配它的局部註冊名稱 (父組件 components 選項的鍵值)。匿名組件不能被匹配。git

  1. computed/watch

在computed/watch中如果有引用到$route,那麼要當心進行判空。不然會出現報錯。

  1. activated & deactivated 能夠在tab頁激活或者失活的時候進行一些操做。好比激活的時候從新刷新列表。

目前已知的問題:

在作了tab頁緩存以後,頁面的Dom節點數量不少,多個頁面並存,帶來了必定的性能問題,還待解決。github

相關文章
相關標籤/搜索