1. 程式人生 > 其它 >Vue 更改keep-alive原始碼,滿足條件性快取(多個頁籤之間切換快取,關閉頁籤重新開啟不快取)

Vue 更改keep-alive原始碼,滿足條件性快取(多個頁籤之間切換快取,關閉頁籤重新開啟不快取)

技術標籤:vuevue

應用場景
當系統開啟多個頁籤時,多個頁籤之間切換去做快取,關閉頁籤重新開啟該頁籤不快取。

解決方案
更改 vue keep-alive原始碼

1、製造一個唯一的key值,在路由引數裡面加一個時間戳

1.1 因keep-alive 使用元件名稱name值作為唯一的key值去做條件快取,但是該專案因書寫不規範導致元件的name值不是唯一的。在路由的引數

    裡面增加一個時間戳

        // 當點選系統左側選單的時候加時間戳

         select(url) {

             //先把開啟的頁籤全部放到store裡面

            //在開啟頁籤裡面找index原因是:找到已經開啟過的頁籤的index,避免開啟重複的多個頁籤
            const isHas = this.$store.state.multiTabPages.findIndex((val)=>{
                return val.name === url
            })
            if(isHas === -1) {
                this.$router.push({name:url, query: {
                    timeStr: new Date().getTime()
                }});
            }else {

               //如果找到已經開啟過的這個頁籤,那就跳轉到它原來的最初開啟的頁籤頁面
                this.$router.push({name:url, query: {
                    timeStr: this.$store.state.multiTabPages[isHas].query.timeStr
                }});
            }
        },

2、給自定義的keep-alive元件唯一的key值

 2.1 從依賴vue包裡面找到keep-alive元件,拿到原始碼。在全域性的元件裡面建立一個自定義的keep-alive元件,注意元件名不要和vue自帶的keep-alive元件名稱相同會有衝突。

 2.2 原始碼是用ts寫的,如果你的專案不支援ts,要先改成js,主要改的是render裡面的邏輯。

 // keep-alive使用的是name值做匹配快取,如下:

 const name = 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 = 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

2.3   把關閉頁籤的路由資訊存入store中,在自定義的keep-alive元件中拿到關閉頁籤的路由資訊

const slot = this.$slots.default // 獲取預設插槽
const vnode = getFirstComponentChild(slot) // 獲取第一個子節點
const componentOptions = vnode && vnode.componentOptions
if (componentOptions) {
  const { cache, keys } = this
  const route = this.$route  
  const key = route.fullPath  //拿到開啟的頁籤的唯一key值(在路由裡面加的時間戳和path組成的一個唯一的key值)


  // 拿到關閉頁籤的路由資訊
  const removeRoute = this.$store.state.removeRoute || []
  if(removeRoute && removeRoute.length > 0){
    let tempKeys = JSON.parse(JSON.stringify(this.keys))
    let diff = tempKeys.filter((val) => { return !removeRoute.includes(val)})
    this.keys = JSON.parse(JSON.stringify(diff))
    for (const key in this.cache) {
      // 在快取的cache物件裡面移除掉已關閉的頁籤的資訊
      if(removeRoute.includes(key)) {
        delete this.cache[key];
      }
    }
  }
  // 如果key對應的vnode存在,則更新key值
  if (cache[key]) {
    vnode.componentInstance = cache[key].componentInstance
    remove(keys, key)
    keys.push(key) // 調整key排序
  } else {
    // 否則將vnode存入快取
    cache[key] = vnode
    keys.push(key)
  }
  vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])

3、使用自定義的keep-alive元件

 3.1 在專案做外層的渲染容器中使用

               <multi-tab></multi-tab>  //頁籤元件
                <KeepAliveCostom>  //自定義keep-alive元件
                  <Common :key="$route.fullPath" />  //頁面渲染的二級容器元件
                </KeepAliveCostom>

頁面容器結構如圖

3.2 給自定義的keep-alive元件傳這個唯一的key值

綜上:根據製造出路由中的一個唯一key值來代替keep-alive元件中的使用name作為唯一的key值,實現keep-alive的條件性渲染

附上更改後的keep-alive原始碼(有刪減)
在這裡插入圖片描述

function pruneCacheEntry (
  cache,
  key,
  keys,
  current
) {
  const cached = cache[key]
  if (cached && (!current || cached.tag !== current.tag)) {
    cached.componentInstance.$destroy()
  }
  cache[key] = null
  remove(keys, key)
}
function getFirstComponentChild (children) {
  if (Array.isArray(children)) {
    for (let i = 0; i < children.length; i++) {
      const c = children[i]
      if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
        return c
      }
    }
  }
}
function isAsyncPlaceholder (node) {
  return node.isComment && node.asyncFactory
}
function isDef (v) {
  return v !== undefined && v !== null
}
function remove (arr, item) {
  if (arr.length) {
    const index = arr.indexOf(item)
    if (index > -1) {
      return arr.splice(index, 1)
    }
  }
}
export default {
  name: 'keep-alive-custom',
  abstract: true,

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

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

  render () {
    const slot = this.$slots.default // 獲取預設插槽
    const vnode = getFirstComponentChild(slot) // 獲取第一個子節點
    const componentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
      const { cache, keys } = this
      const route = this.$route
      const key = route.fullPath //拿到開啟的頁籤的唯一key值

      // 拿到關閉頁籤的路由資訊
      const removeRoute = this.$store.state.removeRoute || []
      if(removeRoute && removeRoute.length > 0){
        let tempKeys = JSON.parse(JSON.stringify(this.keys))
        let diff = tempKeys.filter((val) => { return !removeRoute.includes(val)})
        this.keys = JSON.parse(JSON.stringify(diff))
        for (const key in this.cache) {
          // 在快取的cache物件裡面移除掉已關閉的頁籤的資訊
          if(removeRoute.includes(key)) {
            delete this.cache[key];
          }
        }
      }
      // 如果key對應的vnode存在,則更新key值
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance
        remove(keys, key)
        keys.push(key) // 調整key排序
      } else {
        // 否則將vnode存入快取
        cache[key] = vnode
        keys.push(key)
      }
      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])
  }
}