【源碼】Vue3 Ref原理邻眷,套娃的藝術(shù)

前言

Vue3有個(gè)Ref API,官網(wǎng)文檔 說明其主要的用處是:1剔交、將一個(gè)原始類型值 (例如肆饶,一個(gè)字符串),變成響應(yīng)式的岖常。2驯镊、當(dāng)解構(gòu)的兩個(gè) property 的響應(yīng)性都會(huì)丟失時(shí),可以將我們的響應(yīng)式對象轉(zhuǎn)換為一組 ref竭鞍。這些 ref 將保留與源對象的響應(yīng)式關(guān)聯(lián)板惑。
下面是對應(yīng)的兩個(gè)例子:

import { ref } from 'vue'

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

count.value++
console.log(count.value) // 1
import { reactive, toRefs } from 'vue'

const book = reactive({
  author: 'Vue Team',
  title: 'Vue 3 Guide',
})

let { author, title } = toRefs(book)

title.value = 'Vue 3 Detailed Guide' // 我們需要使用 .value 作為標(biāo)題,現(xiàn)在是 ref
console.log(book.title) // 'Vue 3 Detailed Guide'

由于 Proxy 只能代理對象偎快,那么Vue3是如何代理原始類型值的呢洒放?
Ref 源碼在 packages/reactivity/src/ref.ts 下,閱讀版本是v3.2.30滨砍,ref.ts只有200多行代碼往湿⊙欤看一下 Ref 是怎么實(shí)現(xiàn)的。

正文

(1)Ref

找到ref函數(shù)领追,其調(diào)用路徑是 ref -> createRef -> new RefImpl他膳,RefImpl是一個(gè)類,其實(shí)現(xiàn)很簡單绒窑,提供一個(gè)value屬性和value的訪問器get棕孙、set

class RefImpl<T> {
  private _value: T

  public readonly __v_isRef = true

  constructor(private _rawValue: T, public readonly _shallow = false) {
    this._value = _shallow ? _rawValue : convert(_rawValue)
  }

  get value() {
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }

  set value(newVal) {
    if (hasChanged(toRaw(newVal), this._rawValue)) {
      this._rawValue = newVal
      this._value = this._shallow ? newVal : convert(newVal)
      trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
    }
  }
}

也就是說些膨,ref的實(shí)現(xiàn)蟀俊,還是使用對象,將原始類型值包一層订雾,存到對象的value屬性上肢预。這也是為什么需要用 count.value 來訪問和設(shè)置屬性值。最終還是要使用對象的訪問器get來收集依賴(track)洼哎、set來觸發(fā)更新(trigger)烫映。我們可以自己簡單實(shí)現(xiàn)一個(gè)ref:

image.png

打開 Vue SFC Playground: ref 可查看和運(yùn)行上面的例子。

(2)toRef噩峦、toRefs

接下來看看ref.tstoRef锭沟、toRefs的實(shí)現(xiàn),查看這兩個(gè)API的使用识补,發(fā)現(xiàn)其就是使用 ref 與源響應(yīng)式對象字段的響應(yīng)式關(guān)聯(lián)起來族淮。其解決的問題是,當(dāng)響應(yīng)式對象的屬性解構(gòu)到新的普通對象newObj時(shí)凭涂,響應(yīng)式會(huì)丟失:

const book = reactive({
  author: 'Vue Team',
  title: 'Vue 3 Guide',
})

const newObj = {
  ...book
}

newObj.author // 收集不到依賴
newObj.author = "Team" // 觸發(fā)不了更新

下面看看toRef瞧筛、toRefs:

image.png

他們的實(shí)現(xiàn)很簡單,和實(shí)現(xiàn)ref的原理一樣导盅,也是包一層,提供一個(gè)value字段揍瑟。當(dāng)getset這個(gè)ref.value時(shí)白翻,操作的是源響應(yīng)式對象。達(dá)到與源響應(yīng)式對象字段的響應(yīng)式關(guān)聯(lián)的目的绢片,從而解決響應(yīng)式丟失問題滤馍。
打開 Vue SFC Playground: toRef、toRefs 可查看自己實(shí)現(xiàn)的toRef底循、toRefs巢株。
image.png

(3)ref.value問題與proxyRefs

當(dāng)我們使用ref時(shí),很多人吐槽熙涤,我們總是要加一個(gè).value阁苞,用起來太不爽了困檩。但我們發(fā)現(xiàn),我們在模板中使用ref的時(shí)候那槽,并不需要.value悼沿,那它是怎么實(shí)現(xiàn)的呢?

ref.ts中還有一個(gè) proxyRefs :

image.png

可以看到骚灸,它就是對包含 ref類型字段的對象再包一層糟趾,把普通對象newObj也轉(zhuǎn)成代理對象!當(dāng)訪問 newObj.foo時(shí)甚牲,判斷newObj.foo是不是一個(gè)ref义郑,如果是則代理到newObj.foo.value

然后我們搜一下proxyRefs在哪里有使用丈钙,可以看到在packages/runtime-core/src/component.ts中非驮,handleSetupResult 會(huì)將 setupResult 傳入 proxyRefs!這就是我們模板中使用 ref 對象(newObj.foo)不用加.value的原因著恩。

image.png

至此院尔,模板中的使用問題解決了。但我們還有個(gè)問題是喉誊,在script中使用還是需要.value:

function add() {
    newObj.foo.value++
}

對于這個(gè)問題邀摆,尤大提了一個(gè)提案 Ref Sugar 的 RFC,即 ref 語法糖伍茄,目前還處理實(shí)驗(yàn)性的(Experimental)階段栋盹。提案的主要內(nèi)容是尤大想提供一個(gè)語法糖$ref。使用$ref敷矫,編譯器編譯時(shí)會(huì)自動(dòng)加上.value例获,這樣我們編寫代碼時(shí)就不用使用.value了:

import { $ref } from 'vue'

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

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

我們通過 Vue Playground: $ref 可以直觀地感受一下。

總結(jié)

至此曹仗,ref的源碼也就看得差不多了榨汤,可以看到這是一個(gè)多重代理的東西。如果我們在模板中使用一個(gè)ref屬性(newObj.foo)怎茫,看看其經(jīng)過的代理:
newObj.foo -> newObj.foo.value -> reactiveObj.foo -> rawObj.foo收壕。

其中rawObj是我們最初的普通對象。所以標(biāo)題說這真是套娃轨蛤。

參考

Vue3 Ref 語法糖蜜宪,告別 .value 的寫法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市祥山,隨后出現(xiàn)的幾起案子圃验,更是在濱河造成了極大的恐慌,老刑警劉巖缝呕,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澳窑,死亡現(xiàn)場離奇詭異斧散,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)照捡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門颅湘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人栗精,你說我怎么就攤上這事闯参。” “怎么了悲立?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵鹿寨,是天一觀的道長。 經(jīng)常有香客問我薪夕,道長脚草,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任原献,我火速辦了婚禮馏慨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘姑隅。我一直安慰自己写隶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布讲仰。 她就那樣靜靜地躺著慕趴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鄙陡。 梳的紋絲不亂的頭發(fā)上冕房,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機(jī)與錄音趁矾,去河邊找鬼耙册。 笑死,一個(gè)胖子當(dāng)著我的面吹牛毫捣,可吹牛的內(nèi)容都是我干的详拙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼培漏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了胡本?” 一聲冷哼從身側(cè)響起牌柄,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎侧甫,沒想到半個(gè)月后珊佣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蹋宦,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年咒锻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冷冗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡惑艇,死狀恐怖蒿辙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情滨巴,我是刑警寧澤思灌,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站恭取,受9級特大地震影響泰偿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜈垮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一耗跛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧攒发,春花似錦调塌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至紊扬,卻和暖如春蜒茄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背餐屎。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工檀葛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人腹缩。 一個(gè)月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓屿聋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親藏鹊。 傳聞我的和親對象是個(gè)殘疾皇子润讥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

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