Vue 3 響應式原理三 - activeEffect & ref

在本篇我們將修復一個小 bug 來繼續(xù)構建我們的響應式代碼,然后實現(xiàn)響應式引用。

繼續(xù)之前的代碼:

...
let product = reactive({ price: 5, quantity: 2 })
let total = 0
let effect = () => {
  total = product.price * product.quantity
}
effect() // 活躍 effect
console.log(total)
product.quantity = 3

// 添加了一段獲取響應式對象的屬性的代碼
console.log('Updated quantity to = ' + product.quantity)
console.log(total)

當我們從響應式對象中獲取屬性時,問題就出現(xiàn)了:

在新增的console.log訪問product.quantity時骤菠,track及它里面的所有方法都會被調(diào)用瞄沙,即使這段代碼不在effect(就是我們常說的副作用)中己沛。我們只想查找并記錄 內(nèi)部調(diào)用了get property (訪問屬性) 的活躍 effect

activeEffect

為了解決這個問題距境,我們首先創(chuàng)建一個activeEffect全局變量申尼,用于存儲當前運行的effect。然后我們將在一個名為effect的新函數(shù)中設置它垫桂。

let activeEffect = null // 運行的 active effect
...
function effect(eff) {
  activeEffect = eff  // 把要運行的匿名函數(shù)賦給 activeEffect
  activeEffect()      // 運行它
  activeEffect = null // 再把 activeEffect 設置為 null
}
let product = reactive({ price: 5, quantity: 2 })
let total = 0
effect(() => {
  total = product.price * product.quantity
})
effect(() => {
  salePrice = product.price * 0.9
})
console.log(`Before updated total (should be 10) = ${total} salePrice (should be 4.5) = ${salePrice}`)
product.quantity = 3
console.log(`After updated total (should be 15) = ${total} salePrice (should be 4.5) = ${salePrice}`)
product.price = 10
console.log(`After updated total (should be 30) = ${total} salePrice (should be 9) = ${salePrice}`)

現(xiàn)在我們不再需要手動調(diào)用 effect师幕。它會在我們新的effect函數(shù)中自動調(diào)用。我們還添加了第二個effect诬滩,然后用console.log測試來驗證輸出霹粥。你可以從 GitHub 上獲取并嘗試所有代碼:vue-3-reactivity

到目前為止一切順利,但我們還需要做一項更改疼鸟,那就是在track函數(shù)中使用我們新的activeEffect后控。

function track(target, key) {
  if (activeEffect) { // <------ Check to see if we have an activeEffect
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map())) 
    }
    let dep = depsMap.get(key) 
    if (!dep) {
      depsMap.set(key, (dep = new Set())) // Create a new Set
    }
    dep.add(activeEffect) // <----- Add activeEffect to dependency map
  }
}

現(xiàn)在運行我們的代碼會輸出:

Ref

我們發(fā)現(xiàn)使用salePrice而不是price來計算總數(shù)應該更準確,于是把第一個effect修改如下:

effect(() => {
  total = salePrice * product.quantity
})

如果我們正在創(chuàng)建一個真實的 store空镜,我們可能會根據(jù)salePrice來計算 total浩淘。然而捌朴,這句代碼不會響應式工作。當product.price更新時馋袜,它會響應式地重新計算salePrice男旗,因為有這個副作用:

effect(() => {
  salePrice = product.price * 0.9
})

但是由于salePrice不是響應式的,所以它的變更不會重新計算 total的影響欣鳖。我們上面的第一個副作用不會重新運行察皇。我們需要一些方法來使salePrice具有響應性,如果你熟悉 Composition API泽台,你可能認為應該使用ref來創(chuàng)建一個響應式引用什荣,那就這樣做吧:

let product = reactive({ price: 5, quantity: 2 })
let salePrice = ref(0)
let total = 0

根據(jù) Vue 文檔,響應性引用采用內(nèi)部值并返回一個具有響應性和可維護的ref對象怀酷。ref對象有一個指向內(nèi)部值的屬性.value稻爬。所以我們需要稍微修改一下我們的effect

effect(() => {
  total = salePrice.value * product.quantity
})
effect(() => {
  salePrice.value = product.price * 0.9
})

我們的代碼現(xiàn)在應該起效了蜕依,當salePrice更新時能正確更新total桅锄。但是我們?nèi)匀恍枰ㄟ^ref定義。這個ref又是怎么實現(xiàn)的呢样眠?我們有兩種方式友瘤。

1. 通過 Reactive 定義 Ref

簡單地通過reactive包裝

function ref(intialValue) {
  return reactive({ value: initialValue })
}

然而,這不是 Vue 3 用真正原始定義 ref 的方式

理解 JavaScript Object Accessors - 對象訪問器

首先需要確保先熟悉對象訪問器(object accessors)檐束,有時也稱為 JavaScript 的 computed 屬性(不要和 Vue 的計算屬性混淆)辫秧。
下面??是 Object Accessors 的一個簡單示例:

let user = {
  firstName: 'Gregg',
  lastName: 'Pollack',
  get fullName() {
    return `${this.firstName} ${this.lastName}`
  },
  set fullName(value) {
    [this.firstName, this.lastName] = value.split(' ')
  },
}
console.log(`Name is ${user.fullName}`)
user.fullName = 'Adam Jahr'
console.log(`Name is ${user.fullName}`)

get fullNameset fullName這兩個獲取/設置fullName值的函數(shù)就是對象訪問器。這是純 JavaScript被丧,不是 Vue 的特性盟戏。

2. 通過 Object Accessors 定義 Ref

在對象訪問器內(nèi)配合使用我們的tracktrigger操作,我們可以這樣定義 ref:

function ref(raw) {
  const r = {
    get value() {
      track(r, 'value')
      return raw
    },
    set value(newVal) {
      raw = newVal
      trigger(r, 'value')
    },
  }
  return r
}

這就是全部了甥桂。

這樣做是因為:ref設計的初衷就是為包裝一個內(nèi)部值而服務柿究,如果用reactive包裹的方式封裝它,這樣的“ref”就允許額外添加屬性格嘁,違背了最初的目的笛求。所以ref不應該被當作一個reactive對象。另外還有出于性能的考慮糕簿,用對象字面量創(chuàng)建ref會更節(jié)省性能。

當我們運行下面??的代碼:

function ref(raw) {
  const r = {
    get value() {
      track(r, 'value')
      return raw
    },
    set value(newVal) {
      raw = newVal
      trigger(r, 'value')
    },
  }
  return r
}
function effect(eff) {
  activeEffect = eff
  activeEffect()
  activeEffect = null
}
let product = reactive({ price: 5, quantity: 2 })
let salePrice = ref(0)
let total = 0
effect(() => {
  total = salePrice.value * product.quantity
})
effect(() => {
  salePrice.value = product.price * 0.9
})
console.log(
  `Before updated quantity total (should be 9) = ${total} salePrice (should be 4.5) = ${salePrice.value}`
)
product.quantity = 3
console.log(
  `After updated quantity total (should be 13.5) = ${total} salePrice (should be 4.5) = ${salePrice.value}`
)
product.price = 10
console.log(
  `After updated price total (should be 27) = ${total} salePrice (should be 9) = ${salePrice.value}`
)

能夠得到我們所期望的:

Before updated total (should be 10) = 10 salePrice (should be 4.5) = 4.5
After updated total (should be 13.5) = 13.5 salePrice (should be 4.5) = 4.5
After updated total (should be 27) = 27 salePrice (should be 9) = 9

salePrice現(xiàn)在是響應式的了狡孔,total在它更新時也同步更新了懂诗。

Vue 3 響應式原理一 - Vue 3 Reactivity
Vue 3 響應式原理二 - Proxy and Reflect
Vue 3 響應式原理三 - activeEffect & ref
Vue 3 響應式原理四 - Computed Values & Vue 3 源碼

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市苗膝,隨后出現(xiàn)的幾起案子殃恒,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件离唐,死亡現(xiàn)場離奇詭異病附,居然都是意外死亡,警方通過查閱死者的電腦和手機亥鬓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門完沪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嵌戈,你說我怎么就攤上這事覆积。” “怎么了熟呛?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵宽档,是天一觀的道長。 經(jīng)常有香客問我庵朝,道長吗冤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任九府,我火速辦了婚禮椎瘟,結果婚禮上,老公的妹妹穿的比我還像新娘昔逗。我一直安慰自己降传,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布勾怒。 她就那樣靜靜地躺著婆排,像睡著了一般。 火紅的嫁衣襯著肌膚如雪笔链。 梳的紋絲不亂的頭發(fā)上段只,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音鉴扫,去河邊找鬼赞枕。 笑死,一個胖子當著我的面吹牛坪创,可吹牛的內(nèi)容都是我干的炕婶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼莱预,長吁一口氣:“原來是場噩夢啊……” “哼柠掂!你這毒婦竟也來了?” 一聲冷哼從身側響起依沮,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤涯贞,失蹤者是張志新(化名)和其女友劉穎枪狂,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宋渔,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡州疾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了皇拣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片严蓖。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖审磁,靈堂內(nèi)的尸體忽然破棺而出谈飒,到底是詐尸還是另有隱情,我是刑警寧澤态蒂,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布杭措,位于F島的核電站,受9級特大地震影響钾恢,放射性物質(zhì)發(fā)生泄漏手素。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一瘩蚪、第九天 我趴在偏房一處隱蔽的房頂上張望泉懦。 院中可真熱鬧,春花似錦疹瘦、人聲如沸崩哩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽邓嘹。三九已至,卻和暖如春险胰,著一層夾襖步出監(jiān)牢的瞬間汹押,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工起便, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留棚贾,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓榆综,卻偏偏與公主長得像妙痹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鼻疮,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

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