vue项目相同详情页面多页签解决缓存痛点

707 阅读3分钟

基于vue2开发的后台管理系统,缓存通过keepAlive标签中的include属性来实现。并修改了keepAlive.js中的源码,发布修改后的vue到公司私库中。

我们知道keepAlive是和页面组件中的name属性深度关联的。

之前用组件name当做缓存标识的时候,存在许多痛点。当从列表页打开进入详情页,而且是可以打开多个详情页,因为都是使用的同一个组件name,通过在router-view中加入key可以保证每次进入不同id的详情单不会带出之前缓存的组件信息。

然后此时再新增一个单据时,因为当前页有缓存,所以带出来的是上一个缓存组件的信息...(刚开始做的时候通过watch监听当前路由中没有queryParams参数,判断为新增,则写一个初始化函数,把内容都置为空值)

删除页签的时候还要做判断,打开页签中是否包含当前预删除的页面,如果存在则缓存name不去除,如果不存在则直接删除预删除页面name。

逻辑和冗余代码都挺多的,每个详情页面都得监听路由,还要写初始化置空方法,偶尔不注意可能某个地方的变量没有置空,还会出现bug。就想着能否用每个页面中的fullpath去做缓存的唯一标识符,因为项目中每个页签的fullpath都是唯一的,然后通过研究调试keepalive.js源码,终于实现了当前功能。

因为本后台管理项目是通过页签点击切换的,所以我的实现方式就是添加页签给cachedViews添加当前页路由的fullpath,删除页签时去除当前路由的fullpath。

在业务代码中,给路由组件加上key值

添加删除缓存key

最后简单说下怎么调试vue源码的。

一开始我直接在nodemodules里面源码打印,debugger都不行,后来我把vue源码下载下来,下载依赖后,修改打印后npm run build打包,直接替换nodemodules的dist文件,可行。

改完源码后,我是发布到公司的npm私库里面。然后项目中固定vue版本,删除依赖后重新下载就可以了。

虽然这样做实现了缓存的功能,但是不知道这样写有没有什么隐患,有做过这方面的小伙伴可以一起讨论一下,大家项目中的缓存是怎么写的。

/*
*@flow 修改后的keep-alive.js
*/

import { isRegExp, remove } from 'shared/util'
import { getFirstComponentChild } from 'core/vdom/helpers/index'

type VNodeCache = { [key: string]: ?VNode };

function matches (pattern: string | RegExp | Array<string>, key: string | Number): boolean {
  if (Array.isArray(pattern)) {
    return pattern.indexOf(key) > -1
  } else if (typeof pattern === 'string') {
    return pattern.split(',').indexOf(key) > -1
  } else if (isRegExp(pattern)) {
    return pattern.test(key)
  }
  /* istanbul ignore next */
  return false
}

function pruneCache (keepAliveInstance: any, filter: Function) {
  const { cache, keys, _vnode } = keepAliveInstance
  for (const key in cache) {
    const cachedNode: ?VNode = cache[key]
    if (cachedNode) {
      if (key && !filter(key)) {
        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',
  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.$watch('include', val => {
      pruneCache(this, key => matches(val, key))
    })
    this.$watch('exclude', val => {
      pruneCache(this, key => !matches(val, key))
    })
  },

  render () {
    const slot = this.$slots.default
    const vnode: VNode = getFirstComponentChild(slot)
    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
      // check pattern
      const key: ?string = vnode.data.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.data.key
      const { include, exclude } = this
      if (
        // not included
        (include && (!key || !matches(include, key))) ||
        // excluded
        (exclude && key && matches(exclude, key))
      ) {
        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])
  }
}

-------------------------------

写错了,只要在router-view上面加个key就行了,源码不需要改