VUE快取:動態keep-alive

LucaLJX發表於2018-12-21

路總歸是有的,就看願不願意劍走偏鋒了。

腦殼疼.png

場景

在最近的開發中,設計有A、B、C三個頁面,試想這樣一個場景需求:

  • 離開B頁面進入C頁面,快取B頁面資料(keepAlive: true)
  • 離開B頁面進入A頁面,不快取B頁面資料(keepAlive: false)

概念

  • keep-alive

    • keep-alive 包裹動態元件時,會快取不活動的元件例項,而不是銷燬它們。和 transition 相似,keep-alive 是一個抽象元件:它自身不會渲染一個 DOM 元素,也不會出現在父元件鏈中。
    • keep-alive: vue文件
  • 元件內的守衛 - beforeRouteLeave

前置背景:keep-alive 元件實現

  • 路由元資訊內新增特定欄位如:keepAlive
  const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: { 
            keepAlive: true 
          }
        }
      ]
    }
  ]
})
複製程式碼
  • 父元件內根據路由中的keepAlive欄位動態使用keep-alive標籤
  class Home extends Vue {

    get keepAlive () {
      // 獲取當前路由的元資訊中的keepAlive欄位
      return this.$route.meta.keepAlive
    }

    private render () {
      return (
        <div>
          {
            !this.keepAlive && <router-view />
          }
          <keep-alive>
            {
              this.keepAlive && <router-view/>
            }
          </keep-alive>
        </div>
      )
    }
  }

  export default Home
複製程式碼

思路

由於現在元件的keep-alive是動態根據路由元資訊中的keepAlive欄位進行動態使用的,所以只要動態改變對應路由元資訊的keepAlive欄位就可以實現動態快取。

實現方案

方案一

  • 利用beforeRouteLeave改變from的keepAlive實現(原思路,網路解決方案之一,有bug)
beforeRouteLeave (to: any, from: any, next: any) {
  // 導航離開該元件的對應路由時呼叫
  // 判斷是否是去往頁面 C 
  if (to.name !== 'C') {
    // 不是去 C 頁面,不快取
    from.meta.keepAlive = false
  } else {
    // 是去 C 頁面,快取
    from.meta.keepAlive = true
  }
  next()
}
複製程式碼

bug:首次去C頁面,再返回B頁面,B並沒有快取,第二次再進入C頁面,B頁面快取,且進A頁面並不能清除B頁面的快取

方案二(網路方案)

  • $destroy()銷燬
beforeRouteLeave (to: any, from: any, next: any) {
  // 導航離開該元件的對應路由時呼叫
  // 判斷是否是去往頁面 C 
  if (to.name !== 'C') {
    // 不是去 C 頁面,不快取
    this.$destroy()
  }
  next()
}
複製程式碼

bug:銷燬之後永遠不會被快取

方案三(網路方案)

  • 根據原始碼看來快取的元件都會設定一個cache屬性,可以通過程式碼強行移除掉。缺點就是沒有徹底銷燬依舊佔記憶體
  • 具體實現參考

方案四(最優解)

  • 利用keep-alive的include屬性,利用vuex動態控制include達到動態管理快取
  • 這邊不做贅述,網上有很多相關示例程式碼,且原理很簡單

特殊場景以上方案均不可實現下的解決方案

特殊場景

  • 此次專案為移動端專案,並未用到keep-alive的include屬性,且如果要加入,則專案很多配置需要修改,比較麻煩

解決方案

  • 解決方案與上面的方案一類似
  • 區別:
    • 不操作beforeRouteLeave中的from物件改變keepAlive
    • 直接操作this.$router中對應路由元資訊的keepAlive
// 操作指定name的路由的元資訊
private changeKeepAlive (parentName: string, name: string, keepAlive: boolean) {
  // @ts-ignore
  this.$router.options.routes.map((item: any) => {
    if (item.name === parentName) {
      item.children.map((a: any) => {
        if (a.name === name) {
          a.meta.keepAlive = keepAlive
        }
      })
    }
  })
}

beforeRouteLeave (to: any, from: any, next: any) {
    // 導航離開該元件的對應路由時呼叫
    // 可以訪問元件例項 `this`
    if (to.name === 'C') {
      this.changeKeepAlive('Home', 'B', true)
    } else {
      this.changeKeepAlive('Home', 'B', false)
    }
    next()
  }
複製程式碼

經測試,這種解決方案就不會出現方案一的bug

BY--LucaLJX (LucaLJX的github)

相關文章