經過前一天 Proxy 的概念大致上有個雛形,或許我們可以思考一下:為什麼 Vue 3 需要 Proxy 呢?
A few minutes later...
其實 Vue 2 時期,響應式系統是基於 Object.defineProperty
,這種方式無法攔截「新增或刪除屬性」,對陣列操作也有許多限制。
到了 Vue 3,核心團隊改採 ES6 Proxy,讓「監聽物件的存取與變更」變得完整、靈活,從根本上解決了 Vue 2 的痛點,這就是為什麼 Proxy === Vue 3 響應式的基石
。
今天先用程式碼跟大家說明 Proxy 怎麼做到響應式!
以下是一個簡化版的 reactive()
,能幫助我們直觀理解 Proxy 如何攔截 get / set 操作:
function reactive(target) {
return new Proxy(target, {
get(obj, key) {
const value = Reflect.get(obj, key)
console.log(`收集依賴: ${key}`) // Vue 內部:把 key 與當前渲染函式建立關聯
return value
},
set(obj, key, value) {
const result = Reflect.set(obj, key, value)
console.log(`觸發更新: ${key}`) // Vue 內部:通知所有依賴該 key 的組件重新渲染
return result
}
})
}
// 使用
const state = reactive({ count: 0 })
console.log(state.count) // 收集依賴: count
state.count++ // 觸發更新: count
這個範例雖然簡化,但也算已經說明了 Vue 3 的核心邏輯:
文字、程式碼好多,還是有點迷茫,怎麼辦?
嗯...簡單來說,我們可以把 Proxy 想成「全能管家」,老闆交代管家就只有兩項核心任務:
這樣說很像又太過簡單扼要,程式碼展示過了,不然我們來說個關於 全能管家 Proxy 的故事吧!
故事是這樣的,幾年前有個富豪意外獲得一個超級寶箱,但是因為他忙也沒空看管,裡面的東西多了還是少了也不太確定,於是打聽到江湖上有一個工作效率超高的全能管家 Proxy !
因為他的工作效率佳,所以特聘他幫忙看管裝滿金幣的寶箱!從那天起,任何人都不能再直接接觸寶箱了,所有進出款都必須透過這位管家。
圖片中有個數字 1 ,它就是寶箱也就是我們在 Vue 中建立的原始 JavaScript 物件,例如
{ count: 0, user: { name: 'kuku' } }
。它靜靜地存放你的資料,因為本身很「被動」,你動了它,它也不會主動通知任何人,畢竟它只是一個裝載資料的物件。
圖片中的數字 2 ,就是被聘請看管寶箱的管家 Proxy ,因為寶箱無法表達所以藉由管家來掌管寶箱內的資料讀取異動。
突然有天,家裡有客人來訪,他看到寶箱好想好想知道寶箱裡有什麼,於是向管家詢問。管家一邊拿出金幣給他看,一邊拿出一個小本本,偷偷記下:「A 組件對『金幣數量』很感興趣。」
Proxy 紀錄的行為叫做「依賴收集 (Dependency Tracking)」。
當 Vue 組件畫面<template>
第一次渲染時,需要讀取state.count
的值;這個「讀取」操作會被管家 (Proxy) 攔截,管家知道是哪個組件正在讀取哪個資料,於是就建立了一個關聯:「這個組件依賴於這個資料」。
看完沒多久,客人突然給了管家一顆新的寶石,讓他放進寶箱。管家放入寶石後,立刻拿出大聲公,對著剛剛在他的小本本上記錄過的所有人(A 組件)大喊:「注意!寶箱裡的財產變了!快來看看新的樣子!」
這叫做「觸發更新 (Trigger Update)」。當程式碼執行
state.count++
時,這個「寫入」操作也被管家 Proxy 攔截了。
管家完成資料更新後,會去翻閱他的小本本,找到所有依賴state.count
的組件,並通知它們:「你關心的資料變了,你需要重新渲染畫面了!」
正是因為有了這位管家,Vue 3 才能精準又高效地知道,當資料變動時,應該更新畫面的哪一個部分,而不是無腦地刷新整個頁面。
好了!今天故事講完了,對於管家有沒有更加熟悉呢?