首頁>技術>

from-> yck:https://juejin.im/post/5d996e3e6fb9a04e3043cc5b

Vue3 使用了 Proxy 替換了原來的 Object.defineProperty 來實現資料響應。

reactive()使用

很簡單,直接Vue引入reactive方法,接收一個物件引數,就實現了資料的響應式:

const state = reactive({ num: 0 })effect(() => { console.log(state.num)})state.num = 100

reactive 內部的核心程式碼簡化如下:

首先判斷傳入的引數型別是否可以用於觀察,目前支援的型別為 Object|Array|Map|Set|WeakMap|WeakSet。

接下來判斷引數的建構函式,根據型別獲得不同的 handlers。這裡我們就統一使用 baseHandlers,因為這個已經覆蓋 99% 的情況了。只有 Set, Map, WeakMap, WeakSet 才會使用到 collectionHandlers。

對於 baseHandlers 來說,最主要的是劫持了 get 和 set 行為,這兩個行為同時也能原生劫持陣列下標修改值及物件新增屬性的行為,這一點非常重要,因為Object.defineProperty就不行。

最後就是構造一個 Proxy 物件完成資料的響應式。相比 Object.defineproperty 一開始就要遞迴遍歷整個物件的做法來說,使用 Proxy 效能會好得多。比如原來 forEach 遍歷:

Object.keys(value).forEach((key) => { defineReactive(value, key, value[key]); });

接下來當我們去使用 state 物件的時候,就能劫持到內部的行為。

讀取時:state.num 就會觸發 get 函式;

修改時:state.num = 100 就會觸發 set 函式。

以下是這兩個函式的核心(TS語法):

function get(target: any, key: string | symbol, receiver: any) { // 獲得結果 const res = Reflect.get(target, key, receiver) track(target, OperationTypes.GET, key) // 判斷是否為物件,是的話將物件包裝成 proxy return isObject(res) ? reactive(res) : res}

對於 get 函式來說,獲取值肯定是最核心的一步驟了。接下來是呼叫 track,這個和 effect 有關,下文再說。最後是判斷值的型別,如果是物件的話就繼續包裝成 Proxy。

function set( target: any, key: string | symbol, value: any, receiver: any): boolean { const result = Reflect.set(target, key, value, receiver) if (是否新增 key) { trigger(target, OperationTypes.ADD, key) } else if (value !== oldValue) { trigger(target, OperationTypes.SET, key) }  return result}

對於 set 函式來說,設定值是第一步驟,然後呼叫 trigger,這也是 effect 中的內容。

簡單來說,如果某個 effect 回撥中有使用到 state.num,那麼這個回撥會被收集起來,並在呼叫 state.num =100 時觸發。

那麼怎麼收集這些內容呢?這就要說說 targetMap 這個物件了。它用於儲存依賴關係,類似以下結構,這個結構會在 effect 檔案中被用到

{ target: { key: Dep }}

先來解釋下三者到底是什麼,這個很重要:

target 就是被 proxy 的物件key 是物件觸發 get 行為以後的屬性。比如 counter.num 觸發了 get 行為,num 就是 keydep 是回撥函式,也就是 effect 中呼叫了 counter.num 的話,這個回撥就是 dep,需要收集起來下次使用

這裡筆者把這些內容脫離原始碼串起來講一下流程。

const counter = reactive({ num: 0 })effect(() => { console.log(counter.num)})counter.num = 7

首先建立一個 Proxy 物件,targetMap 會把這個物件收集起來當做 key。

接下來呼叫 effect 回撥的時候會把這個回撥儲存起來,用於下面的依賴收集。在呼叫的過程中會觸發 counter 的 get 函式,內部呼叫了 track 函式,這個函式會使用到 targetMap。

這裡首先通過 target 從 targetMap 中取到一個物件,這個物件也就是 target 所有的依賴關係。那麼對於 counter.num 來說,num 就是這個物件的 key(這裡如果有點模糊的話可以先看下上面的資料結構),值是一個依賴回撥的集合,因為 counter.num 可能會被多個地方依賴到。

回撥執行完畢以後會把儲存的回撥銷燬掉。

當我們呼叫 counter.num = 7 時,觸發 set 函式,內部呼叫 trigger 函式,同樣會使用到 targetMap。

同樣通過 target 取到一個物件,然後通過 key 也就是 num 去取出依賴集合,最後遍歷這個集合執行裡面所有的回撥函式。

另外對於 computed 來說,內部也是使用到了 effect,無非它的回撥不會在呼叫 effect 後立即執行,只有當觸發 get 行為以後才會執行回撥並進行依賴收集,舉個例子:

const state= reactive({ num: 0 })const cValue = computed(() => state.num)state.num = 1

對於以上程式碼來說,computed 的回撥永遠不會執行,只有當使用到了 cValue.value 時才會執行回撥,然後接下來的操作就和上面的沒區別了。

875

TRIGGER

  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • OpenJS 基金會宣佈第一個孵化專案:Node Version Manager