Vue 中有一個特別好用的組件 keep-alive 組件,咱們在不少場景下,都是能夠藉助於這個組件來提高咱們的產品體驗,基本上0成本實現緩存效果。用的最多的場景就是和路由搭配,緩存不怎麼更新的路由頁面。html
那這麼好用的功能,背後是怎麼實現的,有哪些能夠學習的,一塊兒來分析下。前端
來自官網的介紹 cn.vuejs.org/v2/api/#kee…vue
能夠看到,他的使用,更多的是跟隨者動態組件一塊兒使用。最核心的就是緩存組件實例,以提高性能。在官網上也有一個tab切換的示例,就是他的一種使用場景 cn.vuejs.org/v2/guide/co…node
既然是一個內置組件,那麼它確定也就是一個按照組件來定義的,Vue 中核心的實如今 github.com/vuejs/vue/b… 這裏git
export default {
// 組件名字
name: 'keep-alive',
// 抽象組件 這是一個沒有對外暴露的組件聲明屬性
// 做用的話,就是不渲染DOM 也不會出如今父組件的children中
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
methods: {
cacheVNode() {
const { cache, keys, vnodeToCache, keyToCache } = this
// 存在須要緩存的節點
if (vnodeToCache) {
const { tag, componentInstance, componentOptions } = vnodeToCache
// 緩存到 cache 對象中
cache[keyToCache] = {
name: getComponentName(componentOptions),
tag,
componentInstance,
}
keys.push(keyToCache)
// 判斷是否超出了 max 最大緩存實例數
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
// 若是超出了 就銷燬超出的
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
this.vnodeToCache = null
}
}
},
created () {
// 初始化緩存對象
this.cache = Object.create(null)
this.keys = []
},
destroyed () {
// 銷燬的時候 全部實例所有銷燬
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted () {
this.cacheVNode()
// 若是這些有更新 同樣須要再次 check 一遍全部的緩存實例 是否應該緩存
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
updated () {
// 更新鉤子 再次緩存
this.cacheVNode()
},
render () {
// 重點 render 的實現
// 能夠得到組件內的默認內容 其實也就是 默認插槽內容
const slot = this.$slots.default
// 找到裏邊第一個組件節點
const vnode: VNode = getFirstComponentChild(slot)
// 經過 vnode 節點能夠得到組件配置項
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// check pattern
const name: ?string = 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: ?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 (cache[key]) {
// 以前緩存過了
// 直接使用以前緩存的組件實例
vnode.componentInstance = cache[key].componentInstance
// 先刪除掉這個 key 而後在push 保證這個 key 是新鮮的 超限check的時候 有用
// make current key freshest
remove(keys, key)
keys.push(key)
} else {
// delay setting the cache until update
// 設置好 vnodeToCache 當更新的時候 再去緩存 參考 updated 鉤子中的邏輯
this.vnodeToCache = vnode
this.keyToCache = key
}
vnode.data.keepAlive = true
}
// 返回的就是內部的節點
return vnode || (slot && slot[0])
}
}
複製代碼
上邊大概就是核心的一個流程:github
你會發現要想很好的理解上述過程,要很好的理解 Vue 的生命週期,能夠參考 cn.vuejs.org/v2/guide/in… 圖以下:api
此外,還有不少關於 vnode 上的屬性,如:componentOptions、key、data、componentInstance、tag 等,以及相配合的在 Vue 中是如何識別和運用這些屬性的:如何不創新新的實例,如何觸發新的生命週期鉤子 activated deactivated 等,若是你對全部的邏輯細節比較感興趣,能夠參考黃老師的 ustbhuangyi.github.io/vue-analysi…數組
咱們能夠理解爲 Vue 爲何提供了內置組件 keep-alive?緩存
在前言的部分,咱們也講了在實際場景中,仍是會遇到很多緩存組件的狀況,在遇到路由場景的時候更甚。markdown
那 Vue 的一個理念就是對開發者很友好,框架作了不少事情,使得開發者能夠專一於自身的邏輯開發工做,這也是爲何全球會有那麼多開發者鍾愛它的緣由之一。那從這個點出發,由於有這麼多的需求,因此 Vue 也就提供了這麼好用的內置組件也就不難理解了。
咱們能夠看出,keep-alive 的組件實現並不複雜,所有文件也就 150 行上下,可是功能卻很強大,全部的功能參考 cn.vuejs.org/v2/api/#kee… 。那麼從這個組件上,咱們能夠學到些什麼東西,有什麼能夠借鑑的嗎?
組件定義雖然很少,可是倒是用到了 Vue 中絕大多數的生命週期鉤子,且是咱們也能常用到的:created、mounted、updated、destroyed。這也是咱們寫好組件的基石,正確理解並運用他們,知道他們的關係和過程,以及在對應的生命週期中適合作什麼樣的事情。
額外提一點,在 created 生命週期鉤子函數中,咱們看到了如何給實例定義一些非響應式的對象,能夠在 created 中直接 this.xxx = xxxx
的這種方式,而不是習慣性的把這些屬性放到 data()
中去定義(會變爲響應式對象,增長額外的開銷),這種技巧值得咱們學習和應用。
在這裏雖然十分簡單,直接返回了默認插槽內容的節點,可是咱們能夠從這個點出發,在一些特殊場景,仍是須要咱們去手寫 render 的,這部分也是須要咱們去熟練運用的,詳細的能夠參考官網 cn.vuejs.org/v2/guide/re… 關於 render 函數的使用以及createElement、數據對象。
keys 是一個數組,咱們知道 keep-alive 還提供了一個能力:max
這個 prop,指定了最多能夠緩存多少組件實例,一旦這個數字達到了,在新實例被建立以前,已緩存組件中最久沒有被訪問的實例會被銷燬掉。注意這裏的關鍵:最久沒有被訪問的實例會被銷燬掉。
這個是怎麼作到的,排序嗎?不用那麼麻煩,經過上邊的分析咱們知道Vue中採用了一個很巧妙的作法:
remove(keys, key)
keys.push(key)
複製代碼
簡單來講,將 keys 中如今對應的 key 刪除掉,而後把這個 key 再 push 到數組最後便可。
經過這種方式,就能夠保障了 keys 這個數組中最尾部的元素就是最新鮮的元素,最開始的元素就是最不新鮮的,在咱們的場景中就能夠對應爲:最近被訪問的實例。
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
複製代碼
能夠看到實際代碼也是這樣的,當超出 max 的時候,就是銷燬的keys[0]
對應的組件實例。
在 destroyed 中,銷燬了全部的實例,以釋放在 cache 對象中緩存的對象們,這個就是對內存的管理,防止內存泄漏問題。這個是明面的內存銷燬邏輯,你們只要注意了就不會遇到內存泄漏的問題了。
可是這個不是絕對的,咱們看一下最新的這個PR github.com/vuejs/vue/p… ,咱們發現解決了兩個內存泄漏的 issue,直觀看起來是不該該存在內存泄漏的纔對。
這裏就驚醒咱們,要注意內存泄漏問題,他可能會是因爲咱們不經意之間寫的代碼所致使的。咱們須要作到怎麼利用開發者工具:
$destroy()
滴滴前端技術團隊的團隊號已經上線,咱們也同步了必定的招聘信息,咱們也會持續增長更多職位,有興趣的同窗能夠一塊兒聊聊。