iT邦幫忙

2024 iThome 鐵人賽

DAY 7
1
Modern Web

Vue 3 初學者:用實作帶你看過核心概念系列 第 7

Vue 3 用實作帶你看過核心概念 - Day 7: 響應式基礎 - Options API

  • 分享至 

  • xImage
  •  

文章背景圖

目錄

  • JavaScript Proxy 物件
  • Options API - 響應式基礎 - this(proxy object)
  • Options API - 數據(data)
  • Options API - 數據深層響應性
  • 總結

前面提到了模板搭配指令及 Mustache 的用法。今天則是要介紹所謂透過gettersetter取值跟賦值的方式在 JavaScript 是怎麼做到的,這部分會跟 Vue 本身的響應式基礎有很大相關。此外,我也會帶著大家理解Options API在處理響應式資料時的差異。

JavaScript Proxy 物件

Vue 3 的響應式系統基於JavaScriptProxy,其核心原理是通過 get 函數來攔截並取得物件屬性的值,並通過set 函數來攔截對目標物件 (target) 的修改。這使得 Vue 可以在數據變更時自動更新視圖,實現響應式的雙向綁定效果。

  • get 處理函式:當讀取 proxy 物件中的屬性時,返回一個自定義的字符串,該字符串由物件本身及其屬性名稱組成。
  • set 處理函式:當賦值時,更新目標物件的屬性值。

以下示範了如何透過原生 JavaScript Proxy 模擬 Vue 的更新機制:

👉 JavaScript Proxy 物件模擬Vue更新機制實作連結

流程說明

  1. 使用javaScript Proxy 監控({ productName: "apple" }物件。
  2. 設置get 函數來攔截屬性值的讀取,以及set 函數來攔截對屬性的修改。
  3. 設置一個定時器,在指定時間觸發回調函數,變更物件的值,並觸發set 函數

JavaScript Proxy允許攔截對物件的操作,並對這些操作進行自定義處理。通過設置處理器(handler),可以監聽物件內部屬性的所有操作,無論是讀取還是賦值,從而統一管理。

JavaScript:

// Proxy 處理器
const handler = {
  // target:目標對象 property:屬性 receiver:proxy
  get: function (target, property, receiver) {
    return `${JSON.stringify(target)}-${property}`;
  },
  // obj:目標對象 prop:屬性 value:取代新值
  set(obj, prop, value) {
    obj[prop] = value;
  }
};

// 渲染函式
const render = () =>{
  document.querySelector(".getText").innerHTML = proxy.productName;
}

// proxy 物件初始化
const proxy = new Proxy({ productName: "apple" }, handler);

// 渲染初始化畫面({ productName: "apple" }) -> 定期器三秒觸發 -> 將目標物件值從 apple 更換成 banana -> 重新渲染新值到畫面上
render()
setTimeout(()=>{
  proxy.productName = 'banana'
  render()
}, 3000)

⭐ 需要注意的是,Proxy 只能應用於物件,不能作用於基礎數據類型(如字符串、數字等),否則會導致錯誤。

javaScript Proxy 放置非純值錯誤

還想知道更多關於 JavaScript Proxy ,可以參考 MDN JavaScript Proxy

Vue 的響應式系統基於 Proxy,並進行了許多優化:

  • 深度監聽:確保嵌套物件的所有層級都能被響應式地追蹤和更新。
  • 視圖更新:當目標物件的資料發生變更時,Vue 會自動將這些變更部分更新到視圖,確保畫面渲染與資料狀態始終保持同步。

Options API 響應式基礎 - this(proxy object)

在使用 Vue 的 Options API 時,由於上下文的關係,我們通常需要使用 this 來訪問組件的資料或方法。那麼,這裡的 this 實際上指向的是什麼對象呢?

我們可以通過 Vue Options API 生命週期中的mounted 方法來舉例說明這個概念。mounted 方法會在 Vue 綁定成功且模板更新完成後觸發,常用來操作 DOM 或進行初始化邏輯。

⭐ mount 函數與 mounted 生命週期掛鉤的區別

  • mount 函數:用於將 Vue 應用實例掛載到網頁的指定元素上。例如,使用 app.mount('#app') 可以將應用實例掛載到具有 id="app" 的 DOM 元素上。
  • mounted 生命週期掛鉤mounted 是 Vue 組件的生命週期方法之一。當組件的模板已經被完全渲染成 DOM 並掛載到頁面上時,mounted 會自動觸發。

👉 Vue3 CDN Options API 實作連結

Vue 實例資料:

const rootComponent = {
  data() {
    return {
      message: "I am Vue!"
    };
  },
  mounted() {
    console.log("this", this);
    // 正確取得 message 變數方式
    console.log("this.message", this.message);
    // 以下方式取不到 message 變數
    console.log("this.data", this.data);
    console.log("this.data.message", this.data.message);
  }
};
....略

瀏覽器 console.log 顯示結果如下。

印出this的結果:
印出 Options API this 是甚麼

印出this.message的結果:
印出 Options API this.message 是甚麼

印出this.datathis.data.message的結果:
印出 Options API this.data 及 this.data.message 結果

實際上,這裡的this指向的是經過 Vue 處理後的Proxy 物件

很多人會誤以為應該使用this.data來訪問組件中的變數,但事實上,如果這樣做,你會發現返回的是 undefined

這是因為 Vue 在內部對this進行了特殊的處理。this是 Vue 代理過的Proxy 物件,它使我們可以直接透過this.xxx來訪問 data 中的變數,而不需要使用this.data.xxx。這種設計使得訪問組件狀態的操作更加直觀和方便,減少了不必要的中間層級。

Options API - 數據(data)

在 Vue 中,響應式變數是指當數據變更時,能夠自動觸發模板的重新渲染,從而更新畫面上的顯示內容。而一般變數則沒有這種能力,當它的值變更時,不會自動觸發模板的更新。

那麼,如果將響應式變數賦值給一般變數,會發生什麼情況呢?

透過以下的案例並搭配三種情境快速了解一下:

👉 Vue Options API 響應式變數與原始值比較實作連結

const rootComponent = {
  data() {
    return {
      message: [2024]
    };
  },
  mounted() {
    // 問題 1 ----
    let newMessage = [2024];
    console.log("Before - 響應式變數", this.message);
    console.log("Before - 一般變數", newMessage);
    console.log("Before 響應式變數與原始值比較", this.message === newMessage); // 結果:false
    // 問題 2----
    this.message = newMessage;
    console.log("After - 響應式變數", this.message);
    console.log("After - 一般變數", newMessage);
    console.log("After 響應式變數與原始值比較", this.message === newMessage); // 結果:false
    // 問題 3---
    newMessage = [2025];
    console.log("一般變數賦值後結果 - 響應式變數", this.message);
    console.log("一般變數賦值後結果 - 一般變數", newMessage);
  }
};
  1. 問題 1 區域,在 Before 階段,為什麼兩個相同的陣列不會相等?

原因在於一個是 Vue 管理的 Proxy 物件,另一個是普通的 JavaScript 陣列物件。即使它們內容相同,因為它們指向不同的引用,結果還是不會相等。

問題 1 區域,在 Before 階段,陣列比較結果

  1. 問題 2 區域,如果將普通變數賦值給響應式變數,為什麼它們還是不會相等?

當你將一個普通變數賦值給響應式變數時,Vue 的響應式系統會將這個普通變數包裹在一個新的 Proxy 代理中進行管理。因此,響應式變數和普通變數的引用不同,即使它們的內容相同,兩者的比較結果仍然是不相等的

問題 2 區域,在 After 階段,普通變數與響應式變數比較結果

  1. 延續問題 2,問題 3 區域,為什麼我改變一般變數時,響應式變數沒有跟著改變?

問題 3 區域,一般變數無響應性

因為一般變數本身不具備響應性,所以它的改變不會觸發 Vue 的響應式系統。只有透過this.message(即 Proxy 代理)來操作,Vue 才能監控並響應這些變化。

data()使用函式的方式返回定義的變數。主要是因為Template透過Getter的方式取值這樣可以確保每個組件實例都有自己獨立的數據副本,彼此不會互相影響。在實際開發中,通常會有多個 Vue 組件實例同時使用,這種設計可以確保數據的獨立性和可靠性。

👉 Vue3 Options API 組件內數據各自獨立不被影響實作連結

以下圖為例,即使使用多個相同的子組件且每個子組件擁有相同的數據,這些數據也是彼此獨立的,不會因為其他子組件的變化而受到影響。

Vue 組件實例都有自己獨立的數據副本

Options API - 數據深層響應性

在預設情況下,Vue 的 Options API 能夠偵測到數據中的物件陣列的變動,並根據這些變動即時更新和渲染視圖。

以下面案例數據來說,不論是改動usrinfo.nameproductListuserinfo.like 都會被 Vue 捕獲到這個操作,並將改動的結果響應式的反應在視圖上。

👉 Vue3 Options API 數據深層響應性實作連結

const rootComponent = {
  data() {
    return {
      // 用戶資訊
      userInfo: {
        name: "Antonio",
        age: 16,
        address: "Taiwan",
        like: ["Vue", "React", "Angular"]
      },
      // 產品列表
      productList: ["Apple", "Banana", "Orange"]
    };
  },
  ...略
}

總結

  1. Vue 3 響應式系統的核心機制:Vue 3 的響應式系統基於 JavaScript 的 Proxy 機制,透過getset函數攔截對象的訪問和修改。這使得 Vue 可以實現深度監控數據的變化,並將變更即時更新到視圖中。
  2. Options API 中的 this 代理對象:在 Options API 中,this 指向的實際上是經過 Vue 處理的 Proxy 物件,這使得你可以通過this.xxx直接訪問data中的響應式變數,而不需要使用this.data.xxx的方式。
  3. 響應式變數與普通變數的區別:Vue 的響應式數據在變更時會觸發模板的重新渲染,但需要注意的是,響應式變數和普通變數之間的比較會因為Proxy的存在而導致結果不同。這意味著即使兩個對象的內容相同,它們的引用不同,因此比較結果可能為false

上一篇
Vue 3 用實作帶你看過核心概念 - Day 6:數據綁定和模板(Template)
下一篇
Vue 3 用實作帶你看過核心概念 - Day 8:計算屬性應用與實踐
系列文
Vue 3 初學者:用實作帶你看過核心概念14
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言