【vue3源碼】九豌注、ref源碼解析

【vue3源碼】九尼摹、ref源碼解析

參考代碼版本:vue 3.2.37

官方文檔:https://vuejs.org/

ref接受一個內(nèi)部值跃惫,返回一個響應(yīng)式的应又、可更改的ref對象石蔗,此對象只有一個指向其內(nèi)部值的property.value

使用

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

源碼解析

export function ref(value?: unknown) {
  return createRef(value, false)
}

ref返回createRef函數(shù)的返回值罕邀。

createRef接收兩個參數(shù):rawValue待轉(zhuǎn)換的值、shallow淺層響應(yīng)式养距。

function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

如果rawValue本就是ref類型的會立即返回rawValue诉探,否則返回一個RefImpl實例。

RefImpl

class RefImpl<T> {
  private _value: T
  private _rawValue: T
  
  // 當(dāng)前ref的依賴
  public dep?: Dep = undefined
  public readonly __v_isRef = true

  constructor(value: T, public readonly __v_isShallow: boolean) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    this._value = __v_isShallow ? value : toReactive(value)
  }

  get value() {
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    newVal = this.__v_isShallow ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = this.__v_isShallow ? newVal : toReactive(newVal)
      triggerRefValue(this, newVal)
    }
  }
}

RefImpl的構(gòu)造器接收兩個值:value棍厌、__v_isShallow是否淺層響應(yīng)式肾胯。

constructor(value: T, public readonly __v_isShallow: boolean) {
  // 獲取原始值,如果是淺層響應(yīng)式耘纱,原始值就是value敬肚;如果不是淺層響應(yīng)式,原始值是value的原始值
  this._rawValue = __v_isShallow ? value : toRaw(value)
  // 響應(yīng)式數(shù)據(jù)束析,如果是淺層響應(yīng)式艳馒,是value;否則轉(zhuǎn)為reactive(只有Object類型才會轉(zhuǎn)為reactive)
  this._value = __v_isShallow ? value : toReactive(value)
}

當(dāng)獲取new RefImpl()value屬性時员寇,會調(diào)用trackRefValue進行依賴收集弄慰,并返回this._value

export function trackRefValue(ref: RefBase<any>) {
  if (shouldTrack && activeEffect) {
    ref = toRaw(ref)
    if (__DEV__) {
      trackEffects(ref.dep || (ref.dep = createDep()), {
        target: ref,
        type: TrackOpTypes.GET,
        key: 'value'
      })
    } else {
      // 收集依賴到ref.dep中
      trackEffects(ref.dep || (ref.dep = createDep()))
    }
  }
}

reactive不同蝶锋,ref的依賴會被保存在ref.dep中陆爽。

當(dāng)修改new RefImpl()value屬性時,會調(diào)用triggerRefValue觸發(fā)依賴扳缕。

set value(newVal) {
  newVal = this.__v_isShallow ? newVal : toRaw(newVal)
  // 當(dāng)newVal與舊原始值不同時墓陈,觸發(fā)依賴
  if (hasChanged(newVal, this._rawValue)) {
    // 更新原始值及響應(yīng)式數(shù)據(jù)
    this._rawValue = newVal
    this._value = this.__v_isShallow ? newVal : toReactive(newVal)
    triggerRefValue(this, newVal)
  }
}

shallowRef

shallowRef的實現(xiàn)同樣通過createRef函數(shù),不過參數(shù)shallowtrue第献。

export function shallowRef(value?: unknown) {
  return createRef(value, true)
}
const state = shallowRef({ count: 1 })

effect(() => {
  console.log(state.value.count)
})

// 不會觸發(fā)副作用
state.value.count = 2

// 可以觸發(fā)副作用
state.value = {
  count: 3
}

為什么state.value.count = 2不觸發(fā)副作用贡必?
state初始化時,state._value就是{ count: 1 }庸毫,一個普通對象仔拟,當(dāng)使用state.value.count = 2設(shè)置值時,會先觸發(fā)get函數(shù)返回state._value飒赃,然后再修改state._value利花,因為state._value是普通對象科侈,所以不會有副作用觸發(fā)。

而當(dāng)使用state.value = { count: 3 }方式進行修改時炒事,會命中set函數(shù)臀栈,因為新的值與舊的原始值內(nèi)存地址不同,所以會觸發(fā)副作用挠乳。

triggerRef

強制觸發(fā)ref的副作用函數(shù)权薯。

export function triggerRef(ref: Ref) {
  triggerRefValue(ref, __DEV__ ? ref.value : void 0)
}

實現(xiàn)原理很簡單,就是主動調(diào)用一下triggerRefValue函數(shù)睡扬。

由于深度響應(yīng)式的ref會自動進行依賴的觸發(fā)盟蚣,所以triggerRef主要應(yīng)用于shallowRef的內(nèi)部值進行深度變更后,主動調(diào)用triggerRef以觸發(fā)依賴卖怜。例如前面的例子:

const state = shallowRef({ count: 1 })

effect(() => {
  console.log(state.value.count)
})

// 不會觸發(fā)副作用
state.value.count = 2

// 主動觸發(fā)副作用
triggerRef(state)

// 可以自動觸發(fā)副作用
state.value = {
  count: 3
}

customRef

創(chuàng)建一個自定義的ref屎开,顯式聲明對其依賴追蹤和更新觸發(fā)的控制方式。

如創(chuàng)建一個防抖ref马靠,即只在最近一次set調(diào)用后的一段固定間隔后再調(diào)用:

import { customRef } from 'vue'

export function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}

來看customRef的實現(xiàn):

export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
  return new CustomRefImpl(factory) as any
}

customRef返回一個CustomRefImpl實例奄抽。

class CustomRefImpl<T> {
  public dep?: Dep = undefined

  private readonly _get: ReturnType<CustomRefFactory<T>>['get']
  private readonly _set: ReturnType<CustomRefFactory<T>>['set']

  public readonly __v_isRef = true

  constructor(factory: CustomRefFactory<T>) {
    const { get, set } = factory(
      () => trackRefValue(this),
      () => triggerRefValue(this)
    )
    this._get = get
    this._set = set
  }

  get value() {
    return this._get()
  }

  set value(newVal) {
    this._set(newVal)
  }
}

CustomRefImpl的實現(xiàn)與RefImpl的實現(xiàn)差不多,都有個valueget甩鳄、set函數(shù)如孝,只不過getset在內(nèi)部會調(diào)用用戶自己定義的getset函數(shù)娩贷。當(dāng)進行初始化時第晰,會將收集依賴的函數(shù)與觸發(fā)依賴的函數(shù)作為參數(shù)傳遞給factory,這樣用戶就可以自己控制依賴收集與觸發(fā)的時機彬祖。

總結(jié)

ref的通過class實現(xiàn)茁瘦,通過class的取值函數(shù)和存值函數(shù)進行依賴的收集與觸發(fā)。

對于深度響應(yīng)式的ref储笑,會在向value屬性賦值過程中甜熔,將新的值轉(zhuǎn)為reactive,以達到深度響應(yīng)式的效果突倍。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腔稀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子羽历,更是在濱河造成了極大的恐慌焊虏,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秕磷,死亡現(xiàn)場離奇詭異诵闭,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門疏尿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瘟芝,“玉大人,你說我怎么就攤上這事褥琐⌒烤悖” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵敌呈,是天一觀的道長贸宏。 經(jīng)常有香客問我,道長驱富,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任匹舞,我火速辦了婚禮褐鸥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赐稽。我一直安慰自己叫榕,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布姊舵。 她就那樣靜靜地躺著晰绎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪括丁。 梳的紋絲不亂的頭發(fā)上荞下,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音史飞,去河邊找鬼尖昏。 笑死,一個胖子當(dāng)著我的面吹牛构资,可吹牛的內(nèi)容都是我干的抽诉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼吐绵,長吁一口氣:“原來是場噩夢啊……” “哼迹淌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起己单,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤唉窃,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后纹笼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體句携,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年允乐,在試婚紗的時候發(fā)現(xiàn)自己被綠了矮嫉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片削咆。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蠢笋,靈堂內(nèi)的尸體忽然破棺而出拨齐,到底是詐尸還是另有隱情,我是刑警寧澤昨寞,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布瞻惋,位于F島的核電站,受9級特大地震影響援岩,放射性物質(zhì)發(fā)生泄漏歼狼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一享怀、第九天 我趴在偏房一處隱蔽的房頂上張望羽峰。 院中可真熱鬧,春花似錦添瓷、人聲如沸梅屉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坯汤。三九已至,卻和暖如春搀愧,著一層夾襖步出監(jiān)牢的瞬間惰聂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工咱筛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留庶近,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓眷蚓,卻偏偏與公主長得像鼻种,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子沙热,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

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