iT邦幫忙

2024 iThome 鐵人賽

DAY 12
3
Modern Web

欸你是要進 Vue 了沒?系列 第 12

欸你是要進 Vue 了沒? - Day12:Vue 你怎麼 DOM 起來了?響應式基礎從 ref 開始(下)

  • 分享至 

  • xImage
  •  

上篇我們提到:
當用 ref 綁定響應式的數據,會產生一個 RefImpl 的物件,而它提供了 value 屬性讓我們存取其中的值。

不過使用的兩個範例都是綁定了數字,今天我們一起來試試看將 ref 綁定「物件」。
但在這之前~請先關注一下它是怎麼運作的!

開始!
/images/emoticon/emoticon31.gif

ref 運作機制

官方文件:這是通過一個基於依賴追蹤的響應式系統實現的。當一個組件首次渲染時,Vue 會追蹤在渲染過程中使用的每一個 ref。然後,當一個 ref 被修改時,它會觸發追蹤它的組件的一次重新渲染。

在標準的 JavaScript 中,檢測普通變量的訪問或修改是行不通的。然而,我們可以通過 getter 和 setter 方法來攔截對象屬性的 get 和 set 操作。

該 .value 屬性給予了 Vue 一個機會來檢測 ref 何時被訪問或修改。在其內部,Vue 在它的 getter 中執行追蹤,在它的 setter 中執行觸發。

從概念上講,你可以將 ref 看作是一個像這樣的對象:

// 偽代碼,並非真正的實現
const myRef = {
  _value: 0,
  get value() {
    track()
    return this._value
  },
  set value(newValue) {
    this._value = newValue
    trigger()
  }
}

這邊的 gettersetter 是原生 JS 的用法,而我們來解釋一下這段偽程式碼想表達什麼:

  • _value:這裡儲存當前的值。
  • get value():存取、紀錄響應式狀態依賴的值。
    當我們存取 ref 的值時,getter 函式會被觸發。
    它做了:
    1. track() 來記得所有依賴這個值的地方。
    2. 回傳 _value
  • set value(newValue):更新任何有關 getter 記錄下來的所有位置。
    當我們修改 ref 的值時,setter 函式會被觸發。
    它做了:
    1. 更新 _value 的值。
    2. 透過 trigger() 通知系統,去觸發 getter 中紀錄的所有依賴這個 value 的地方,去進行重新渲染和更新(例如資料或介面)。

統整!

  • getter:是「依賴追蹤」的攔截器。
    當你存取數據時,getter 會被觸發,紀錄所有依賴這個數據的地方,並回傳值。
  • setter:是「重新渲染」的觸發機制。
    當你修改數據時,setter 會更新值,並通知系統重新渲染依賴這個數據的 UI。

用量體重和飲食計畫來比喻的話~

昨天 ++ 量完體重後,去諮詢了營養師!
營養師要求 ++ 在「體重紀錄表上記錄他的體重」,且營養師會「根據這個體重,即時制定鏟肉計畫」。

可以用這個情境模擬響應式系統的工作模式:

  • 體重紀錄表(getter):紀錄體重(_value),當 ++ 想知道自己的體重的時候,就可以來這邊查閱(就像使用 getter 獲取 _value)。
  • 營養師提供的鏟肉計畫(setter):營養師知道了 ++ 的體重(來自 getter 中紀錄的 _value),會根據這個體重的情況調整飲食建議和計畫(就像使用 setter 更新數據和介面)。

小道消息

以上的偽程式碼,是官方文件用原生 JS 的用法 gettersetter 來比喻資料變動後,是如何更改介面的響應式原理。

在 Vue 2 中主要就是用此種實現方式,而在 Vue 3 中主要使用 Proxy 物件的操作。
想了解更細的先坐火箭到這裡

ref 的深層響應性

官方文件:Ref 可以持有任何類型的值,包括深層嵌套的對象、數組或者 JavaScript 內置的數據結構,例如 Map。

非原始值將通過 reactive() 轉換為響應式代理,該函數將在後面討論。

深層嵌套的對象?

上篇範例一、二的操作,是使用「字串、數字等等」這種「原始值」綁定的值。
而「深層」指的是:使用「物件」這種類型來綁定的值。

原始值?非原始值?

MDN:除了物件以外的所有值,都是原始定義的值(值意味著不能被改變)。

因此 ref 的深層響應性,就是 ref 綁定一個「物件」的這個行為。
我們馬上用「物件」來做範例:

範例三

<script setup>
import { ref } from "vue";

const obj = { title: "今天是鐵人賽第 12 天" };
const refObj = ref(obj);
console.log(`refObj`, refObj);
</script>

瀏覽器上會看到:
https://ithelp.ithome.com.tw/upload/images/20240925/2016913982NdsFOjcC.png

_value 處,有個 Proxy,然後它看起來是個物件⋯⋯
https://ithelp.ithome.com.tw/upload/images/20240925/201691396PVmWTaj5I.png
(迷惑中)

咦?還可以正常解包嗎

如上篇所證實的:
<template> 使用 ref 綁定的值時, Vue 會幫我們解包,讓我們直接拿到 RefImpl_value 的值。

但現在 _value 的值是一個 Proxy 物件耶?

先以原預期的方式操作試試看:

<script setup>
import { ref } from "vue";

const obj = { title: "今天是鐵人賽第 12 天" };
const refObj = ref(obj);
console.log(`refObj`, refObj);
</script>

<template>{{ refObj }}</template>

https://ithelp.ithome.com.tw/upload/images/20240925/20169139SoOuv6ztS3.png

哭了,拿不到。
不過⋯⋯如果 Proxy 是個物件⋯⋯那我應該可以 .title 拿到值?

<script setup>
import { ref } from "vue";

const obj = { title: "今天是鐵人賽第 12 天" };
const refObj = ref(obj);
console.log(`refObj`, refObj);
</script>

<template>
  <p>用 refObj 取值:{{ refObj }}</p>
  <p>用 refObj.title 取值:{{ refObj.title }}</p>
</template>

https://ithelp.ithome.com.tw/upload/images/20240925/20169139yD13JZYvW2.png

拿到了!

因此證實了:當 ref 綁定了「物件」,是不會解包這個物件中的 Proxy 物件喔。

Proxy 物件?

難道是上述說的

非原始值將通過 reactive() 轉換為響應式代理

其中的轉換工具嗎(這不是故意要營造氣氛,是我真的也還不知道 XD

小結

相信大家看到這邊會覺得怎麼有點爛尾 QQ
配速的小蝸牛明天就來講 Proxy 到底是什麼囉!
/images/emoticon/emoticon51.gif

範例 code ⬇️

https://github.com/Jamixcs/2024iThome-jamixcs/tree/main/src/components/day12

參考資料


上一篇
欸你是要進 Vue 了沒? - Day11:Vue 你怎麼 DOM 起來了?響應式基礎從 ref 開始(上)
下一篇
欸你是要進 Vue 了沒? - Day13:Vue 你怎麼 DOM 起來了?都學到這了依舊沒辦法繞過的 JS 原生 Proxy 概念
系列文
欸你是要進 Vue 了沒?13
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言