Vue源碼探究-組件的持久活躍

Vue源碼探究-組件的持久活躍

*本篇代碼位于vue/src/core/components/keep-alive.js

較新版本的Vue增加了一個內(nèi)置組件 keep-alive邮旷,用于存儲組件狀態(tài),即便失活也能保持現(xiàn)有狀態(tài)不變,切換回來的時候不會恢復到初始狀態(tài)斗搞。由此可知翠胰,路由切換的鉤子所觸發(fā)的事件處理是無法適用于 keep-alive 組件的劣坊,那如果需要根據(jù)失活與否來給予組件事件通知战授,該怎么辦呢囤攀?如前篇所述丈氓,keep-alive 組件有兩個特有的生命周期鉤子 activateddeactivated周循,用來響應(yīng)失活狀態(tài)的事件處理强法。

來看看 keep-alive 組件的實現(xiàn),代碼文件位于 components 里湾笛,目前入口文件里也只有 keep-alive 這一個內(nèi)置組件饮怯,但這個模塊的分離,會不會預示著官方將在未來開發(fā)更多具有特殊功能的內(nèi)置組件呢嚎研?

// 導入輔助函數(shù)
import { isRegExp, remove } from 'shared/util'
import { getFirstComponentChild } from 'core/vdom/helpers/index'

// 定義VNodeCache靜態(tài)類型
// 它是一個包含key名和VNode鍵值對的對象蓖墅,可想而知它是用來存儲組件的
type VNodeCache = { [key: string]: ?VNode };

// 定義getComponentName函數(shù),用于獲取組件名稱临扮,傳入組件配置對象
function getComponentName (opts: ?VNodeComponentOptions): ?string {
  // 先嘗試獲取配置對象中定義的name屬性论矾,或無則獲取標簽名稱
  return opts && (opts.Ctor.options.name || opts.tag)
}

// 定義matches函數(shù),進行模式匹配杆勇,傳入匹配的模式類型數(shù)據(jù)和name屬性
function matches (pattern: string | RegExp | Array<string>, name: string): boolean {
  // 匹配數(shù)組模式
  if (Array.isArray(pattern)) {
    // 使用數(shù)組方法查找name贪壳,返回結(jié)果
    return pattern.indexOf(name) > -1
  } else if (typeof pattern === 'string') {
    // 匹配字符串模式
    // 將字符串轉(zhuǎn)換成數(shù)組查找name,返回結(jié)果
    return pattern.split(',').indexOf(name) > -1
  } else if (isRegExp(pattern)) {
    // 匹配正則表達式
    // 使用正則匹配name蚜退,返回結(jié)果
    return pattern.test(name)
  }
  /* istanbul ignore next */
  // 未匹配正確模式則返回false
  return false
}

// 定義pruneCache函數(shù)闰靴,修剪keep-alive組件緩存對象
// 接受keep-alive組件實例和過濾函數(shù)
function pruneCache (keepAliveInstance: any, filter: Function) {
  // 獲取組件的cache,keys钻注,_vnode屬性
  const { cache, keys, _vnode } = keepAliveInstance
  // 遍歷cache對象
  for (const key in cache) {
    // 獲取緩存資源
    const cachedNode: ?VNode = cache[key]
    // 如果緩存資源存在
    if (cachedNode) {
      // 獲取該資源的名稱
      const name: ?string = getComponentName(cachedNode.componentOptions)
      // 當名稱存在 且不匹配緩存過濾時
      if (name && !filter(name)) {
        // 執(zhí)行修剪緩存資源操作
        pruneCacheEntry(cache, key, keys, _vnode)
      }
    }
  }
}

// 定義pruneCacheEntry函數(shù)蚂且,修剪緩存條目
// 接受keep-alive實例的緩存對象和鍵名緩存對象,資源鍵名和當前資源
function pruneCacheEntry (
  cache: VNodeCache,
  key: string,
  keys: Array<string>,
  current?: VNode
) {
  // 檢查緩存對象里是否已經(jīng)有以key值存儲的資源
  const cached = cache[key]
  // 如果有舊資源并且沒有傳入新資源參數(shù)或新舊資源標簽不同
  if (cached && (!current || cached.tag !== current.tag)) {
    // 銷毀該資源
    cached.componentInstance.$destroy()
  }
  // 置空key鍵名存儲資源
  cache[key] = null
  // 移除key值的存儲
  remove(keys, key)
}

// 定義模式匹配接收的數(shù)據(jù)類型
const patternTypes: Array<Function> = [String, RegExp, Array]

// 導出keep-alive組件實例的配置對象
export default {
  // 定義組件名稱
  name: 'keep-alive',
  // 設(shè)置abstract屬性
  abstract: true,
  // 設(shè)置組件接收的屬性
  props: {
    // include用于包含模式匹配的資源幅恋,啟用緩存
    include: patternTypes,
    // exclude用于排除模式匹配的資源杏死,不啟用緩存
    exclude: patternTypes,
    // 最大緩存數(shù)
    max: [String, Number]
  },

  created () {
    // 實例創(chuàng)建時定義cache屬性為空對象,用于存儲資源
    this.cache = Object.create(null)
    // 設(shè)置keys數(shù)組捆交,用于存儲資源的key名
    this.keys = []
  },

  destroyed () {
    // 實例銷毀時一并銷毀存儲的資源并清空緩存對象
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },

  mounted () {
    // DOM加載完成后识埋,觀察include和exclude屬性的變動
    // 回調(diào)執(zhí)行修改緩存對象的操作
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  },

  render () {
    // 實例渲染函數(shù)
    // 獲取keep-alive包含的子組件結(jié)構(gòu)
    // keep-alive組件并不渲染任何真實DOM節(jié)點,只渲染嵌套在其中的組件資源
    const slot = this.$slots.default
    // 將嵌套組件dom結(jié)構(gòu)轉(zhuǎn)化成虛擬節(jié)點
    const vnode: VNode = getFirstComponentChild(slot)
    // 獲取嵌套組件的配置對象
    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    // 如果配置對象存在
    if (componentOptions) {
      // 檢查是否緩存的模式匹配
      // check pattern
      // 獲取嵌套組件名稱
      const name: ?string = getComponentName(componentOptions)
      // 獲取傳入keep-alive組件的include和exclude屬性
      const { include, exclude } = this
      // 如果有included零渐,且該組件不匹配included中資源
      // 或者有exclude窒舟。且該組件匹配exclude中的資源
      // 則返回虛擬節(jié)點,不繼續(xù)執(zhí)行緩存
      if (
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }

      // 獲取keep-alive組件的cache和keys對象
      const { cache, keys } = this
      // 獲取嵌套組件虛擬節(jié)點的key
      const key: ?string = vnode.key == null
        // 同樣的構(gòu)造函數(shù)可能被注冊為不同的本地組件诵盼,所以cid不是判斷的充分條件
        // 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
      // 如果緩存對象里有以key值存儲的組件資源
      if (cache[key]) {
        // 設(shè)置當前嵌套組件虛擬節(jié)點的componentInstance屬性
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest
        // 從keys中移除舊key惠豺,添加新key
        remove(keys, key)
        keys.push(key)
      } else {
        // 緩存中沒有該資源,則直接存儲資源风宁,并存儲key值
        cache[key] = vnode
        keys.push(key)
        // 如果設(shè)置了最大緩存資源數(shù)洁墙,從最開始的序號開始刪除存儲資源
        // prune oldest entry
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
      }

      // 設(shè)置該資源虛擬節(jié)點的keepAlive標識
      vnode.data.keepAlive = true
    }
    // 返回虛擬節(jié)點或dom節(jié)點
    return vnode || (slot && slot[0])
  }
}

keep-alive 組件的實現(xiàn)也就這百來行代碼,分為兩部分:第一部分是定義一些處理具體實現(xiàn)的函數(shù)戒财,比如修剪緩存對象存儲資源的函數(shù)热监,匹配組件包含和過濾存儲的函數(shù);第二部分是導出一份 keep-alive 組件的應(yīng)用配置對象饮寞,仔細一下這跟我們在實際中使用的方式是一樣的孝扛,但這個組件具有已經(jīng)定義好的特殊功能
列吼,就是緩存嵌套在它之中的組件資源,實現(xiàn)持久活躍苦始。

那么實現(xiàn)原理是什么寞钥,在代碼里可以清楚得看到,這里是利用轉(zhuǎn)換組件真實DOM節(jié)點為虛擬節(jié)點將其存儲到 keep-alive 實例的 cache 對象中陌选,另外也一并存儲了資源的 key 值方便查找理郑,然后在渲染時檢測其是否符合緩存條件再進行渲染。keep-alive 的實現(xiàn)就是以上這樣簡單咨油。


最初一瞥此段代碼時您炉,不知所云。然而當開始逐步分析代碼之后役电,才發(fā)現(xiàn)原來只是沒有仔細去看邻吭,誤以為很深奧,由此可見宴霸,任何不用心的行為都不能直抵事物的本質(zhì),這是借由探索這一小部分代碼而得到的教訓膏蚓。因為在實際中有使用過這個功能瓢谢,所以體會更深,有時候難免會踩到一些坑驮瞧,看了源碼的實現(xiàn)之后氓扛,發(fā)現(xiàn)原來是自己使用方式不對,所以了解所用輪子的實現(xiàn)還是很有必要的论笔。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末采郎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子狂魔,更是在濱河造成了極大的恐慌蒜埋,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,002評論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件最楷,死亡現(xiàn)場離奇詭異整份,居然都是意外死亡,警方通過查閱死者的電腦和手機籽孙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評論 3 400
  • 文/潘曉璐 我一進店門烈评,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人犯建,你說我怎么就攤上這事讲冠。” “怎么了适瓦?”我有些...
    開封第一講書人閱讀 169,787評論 0 365
  • 文/不壞的土叔 我叫張陵竿开,是天一觀的道長谱仪。 經(jīng)常有香客問我,道長德迹,這世上最難降的妖魔是什么芽卿? 我笑而不...
    開封第一講書人閱讀 60,237評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮胳搞,結(jié)果婚禮上卸例,老公的妹妹穿的比我還像新娘。我一直安慰自己肌毅,他們只是感情好筷转,可當我...
    茶點故事閱讀 69,237評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著悬而,像睡著了一般呜舒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上笨奠,一...
    開封第一講書人閱讀 52,821評論 1 314
  • 那天袭蝗,我揣著相機與錄音,去河邊找鬼般婆。 笑死到腥,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蔚袍。 我是一名探鬼主播乡范,決...
    沈念sama閱讀 41,236評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼啤咽!你這毒婦竟也來了晋辆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,196評論 0 277
  • 序言:老撾萬榮一對情侶失蹤宇整,失蹤者是張志新(化名)和其女友劉穎瓶佳,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鳞青,經(jīng)...
    沈念sama閱讀 46,716評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡涩哟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,794評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了盼玄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贴彼。...
    茶點故事閱讀 40,928評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖埃儿,靈堂內(nèi)的尸體忽然破棺而出器仗,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,583評論 5 351
  • 正文 年R本政府宣布精钮,位于F島的核電站威鹿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏轨香。R本人自食惡果不足惜忽你,卻給世界環(huán)境...
    茶點故事閱讀 42,264評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望臂容。 院中可真熱鬧科雳,春花似錦、人聲如沸脓杉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽球散。三九已至尿赚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蕉堰,已是汗流浹背凌净。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留屋讶,地道東北人冰寻。 一個月前我還...
    沈念sama閱讀 49,378評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像丑婿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子没卸,可洞房花燭夜當晚...
    茶點故事閱讀 45,937評論 2 361

推薦閱讀更多精彩內(nèi)容