【源碼】Reactive 源碼

前言

用了很長時間的 componsition-api 了,最近想看看源碼,抱著單純的學習心態先從 reactive 開始吧。

個人習慣:

  • 看代碼要帶著問題去看,不要盲目的去看
  • 問題就是這次看源碼的主線,要圍繞著主線去展開,過程中和主線沒有多大關系的該忽略掉就忽略掉
  • 開源項目一般都封裝的比較好,有可能一個函數中會引用多個文件中的函數,每次跳轉的時候將跳轉的目錄記下來,避免跳著跳著就不知道跳哪去了
  • 進入到一個文件中先將函數都收起來,便于查看

前置準備

  • vue-next 從 github 上把項目 clone 下來。

  • 通過 yarn install 安裝依賴。

  • package.jsondev 腳本增加 sourcemap 最終命令為: "dev": "node scripts/dev.js --sourcemap"

  • 因為 vue3 是通過 rollup 打包的,所以還需要安裝 rollup

  • 執行 npm run dev 在 package/vue/ 目錄下會生成一個 dist 文件夾。

  • 在 package/vue/examples/ 目錄下新建 init.html 文件。

    <!DOCTYPE html>
    <html lang="en">
    <body><div id="app"><h1>hello</h1></div><script src="../dist/vue.global.js"></script><script>const { watch, watchEffect, createApp, reactive } = Vuedebuggerconst data = reactive({a: 1,b: 2,count: 0})</script>
    </body>
    </html>
    
  • 然后將這個文件以服務的形式跑起來進入 debug,通過斷點可以進入到 reactive 函數。

數據響應式 Reactive

reactive 函數一開始就判斷了如果傳入的 target 如果是一個只讀的對象則 return target

緊接著調用了 createReactiveObject 函數,先來看一下函數對應的參數:

  1. target 將要被代理的對象
  2. 是否只讀
  3. baseHandlerscollectionHandlers 都是 proxyhandler,對應的實參分別是 mutableHandlersmutableCollectionHandlers,根據 target 類型來決定 proxyhandler
  4. reactiveMap 是當前文件中聲明的一個常量,用于存儲依賴,現在只需要它是一個 weakMap 類型數據就好

函數一開始對 target 做了幾種情況的判斷,針對不同情況做了對應的 return

主要關注點是這一段代碼

const proxy = new Proxy(target,targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)

這里會根據 target 的類型來選擇對應的 handlertargetType 是調用 getTargetType 函數的返回值,targetTypeMap/Set/WeakMap/WeakSet 的時候會將 collectionHandlers 傳入 Proxy,反之采用 baseHandlers

baseHandlers

baseHandlers 對應的 mutableHandlers

get

get 對應的 createGetter 的返回值

createGetter 接收兩個參數 isReadonlyshallow,在 get 方法中先判斷了三種特殊情況,針對每種不同情況 return 了不同的值,其中在這幾個判斷中用到的 shallowReadonlyMap/readonlyMap/shallowReactiveMap 它們的類型和 reactiveMap 的類型都一樣都是 weakMap,只不過是對應不同的狀態。

下邊判斷了是不是數組,如果是數組并且 isReadonlyfalsekeyincludes/indexOf/lastIndexOf/push/pop/shift/unshift/splice 其中的一個的話則執行

return Reflect.get(arrayInstrumentations, key, receiver)

也就是對上述那些方法進行了重寫。這段代碼是為了解決邊緣情況

includes/indexOf/lastIndexOf 是為了避免產生以下情況

const obj = {}
const arr = reactive([obj])
arr.indexOf(obj) // -1 正常情況下應該返回的是 0

push/pop/shift/unshift/splice 是為了避免這些改變數組長度的方法在某些情況下進入死循環

再往下獲取了 keytarget 中對應的值

const res = Reflect.get(target, key, receiver)

接著判斷 keysymbol 的情況。如果 isReadonlyfalse 則調用 track 函數進行依賴收集。

track 這個函數往后放一下,看完這個 get 函數后再回頭來看 track

接著又判斷了 createGetter 傳入的 shallowtrueresref 類型的這兩種情況。

如果 res 還是一個對象并且 isReadonlyfalse 則遞歸調用 reactive 函數,反之調用 readonly 函數。

從調用 reactive 函數這里可以看出,vue3 中的響應式和 vue2 的差別不僅在 definePropertyproxy 上,在處理響應式的時機上也有變化,defineProperty 是一上來就將 target 上的所有屬性都變成響應式的,但是 vue3 是在當你去讀取這個 key 的時候,采取將 key 對應的 value 轉換成響應式的。

接下來去看一下 track 函數,這個函數的作用是用來收集依賴的。

一開始是一個判斷條件,接下來的這段代碼是為了獲取當前 key 對應的數據同時構造一個數據結構

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()))
}

構造出來的數據結構是這樣的

targetMap = {target: {key: [dep,...] // dep, set 類型} // map 類型
} // weakMap 類型

后邊將 activeEffect 添加到 dep 中。這里的 activeEffect 其實就是我們要收集的依賴。

后邊又將 dep 添加到了 activeEffectdeps 中,這一塊當時在看的時候并不明白,后來是查資料明白的,我們放到后邊說。如果是開發環境還調用了 onTrack 函數,這個函數在文檔中有說,用于調試偵聽器的行為。

至此,track 函數就結束了。

遇到的問題
  • activeEffect.deps.push(dep) 是什么,為什么要這樣做

    語義上來看是將當前 key 所對應的 dep push 到了 activeEffectdeps 中,但是為什么要這樣做還有待思考。網上查了一番,這篇文章寫的還是很不錯的,對這個問題點也有一定的解析。

    文章內的總結: 這個操作是在為在 reactiveEffect 方法中提到的 cleanup 方法做準備,每次收集 activeEffect 之前,會先將 activeEffect 中的 deps 清空,然后再進行收集依賴。先清空再收集就是為了避免如果 activeEffect 中有在特定條件下才會觸發的依賴收集,之前已經收集過了,但是這次不需要收集,所以會先把之前收集的清空掉,然后再針對當前這次需要收集的依賴進行收集,保證當前收集的依賴肯定是當前需要被收集的。這里又引申除了 reactiveEffect 方法,這個方法在 watchEffect 中會用到,本文不涉及,所以不展開說。

    set

set 對應的 createSetter 的返回值

createSetter 接收一個參數 shallow 用來表示是不是淺層對象,然后直接 returnset 函數。

首先判斷了不是淺層對象則處理 oldValueref 類型,但 newValue 不是 ref 類型的情況,因為 ref 類型的數據已經是響應式的了,所以不需要再次通過 trigger 函數來再次觸發依賴。

refreactive 差不多,都可以返回一個響應式的數據,但是 ref 需要通過 .value 的形式來獲取值。

后邊的代碼就是判斷 target 是不是數組,用 Reflect.set()value 添加到 target 中,后續又判斷了 key 在不在 target 中,如果 key 不在 target 中則按 ADD 類型調用 trigger 函數觸發依賴,反之以 SET 類型調用 trigger 函數。調用完之后將 Reflect.set() 的返回值 return 出去。

接下來看一下 trigger 函數

targetMap 中獲取 target 對應的依賴,沒有則 return

定義了 set 類型的 effectsadd 方法,add 方法主要是將傳入的 deps 遍歷,然后將每個 effect 添加到 effects 中,這樣 effects 中就保存了所有待執行的 effect

下邊就是根據傳入的 type 來執行對應的 add 函數。

再往下就定義了 run 函數,它負責來執行 effect,如果是開發環境 effect 中有 onTrigger 方法的話會先執行 onTrigger 方法,這個方法和 track 函數中調用的 onTrack 函數是一個意思,文檔中有說,用于調試偵聽器的行為。如果 effect 中有調度器的話會選擇用調度器來執行 effect 否則直接執行 effect

run 函數的定義完了就是遍歷 effects,將 run 函數傳入并執行。

以上就是 trigger 函數的所有了。

deleteProperty

這個方法就沒什么好說的了,用 Reflect.deleteProperty() 執行刪除,判斷這個 keytarget 自身的則調用 trigger 觸發依賴,然后 return Reflect.deleteProperty() 的返回值。

has

執行 Reflect.has(),在 key 是非 Symbol 類型的時候調用 track 函數收集依賴,然后 return Reflect.has() 的返回值。

ownKeys

調用 track 收集依賴,return Reflect.ownKeys(target)

collectionHandler

collectionHandlers 對應的 mutableCollectionHandlers

get

get 對應的 createInstrumentationGetter 的返回值

createInstrumentationGetter 函數定義了 isReadonlyshallow 兩個參數,這里在調用的時候傳入的都是 false

方法內也根據這兩個參數去獲取了對應的 handlers 定義為 instrumentations,后邊判斷了 key 的三種特殊情況,這里的 key 代表的是調用 get 方法時的 key,根據 key 的不同 return 對應的值。

最后判斷 key 是不是 instrumentations 自身的屬性,如果是則 Reflect.get() 傳入的第一個值是 instrumentations,反之直接將 target,最后將 Reflect.get() 的返回值 return。

這個 collectionHandlers 不進行具體展開,主要還是圍繞著我們的主題進行。

collectionHandlers 到此結束。

總結

  1. vue3 的響應式將 target 對象傳入 proxy,然后利用 handlers,在執行 get/has/ownKeys 等獲取值方法的時候進行依賴收集,在執行 set/deleteProperty 等更改值方法的時候進行觸發依賴。

  2. vue3 對于 target 的某一個 keyvalue 值還是對象的時候,只有在讀取到這個 key 的時候才將 value 進行響應式處理,而 vue2 的處理是初始化的時候直接將所有屬性及屬性值進行響應式處理。

    以上就是我關于 reactive 源碼的閱讀過程,在我最終讀完之后去跑 demo 的時候發現進入到 track 函數的時候,在一開始那個判斷的地方就被 return 出去了,并沒有真正的收集依賴。這也就產生了我的另外一個問題:什么時候才會收集依賴呢。有了這個問題,可以繼續把源碼看下去了

參考鏈接

  • 源碼系列:Vue3深入淺出(一)
  • Vue3 文檔
  • Vue3最啰嗦的Reactivity數據響應式原理解析

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/911831.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/911831.shtml
英文地址,請注明出處:http://en.pswp.cn/news/911831.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

銀河麒麟 | ubuntu 安裝國產達夢DM8數據庫(安裝+外網通+IDEA連接)

目錄 官網下載安裝 下載安裝包 創建安裝用戶組dinstall 創建安裝用戶dmdba并指定組 創建DM8軟件安裝目錄修改權限 檢查、修改系統資源限制 解壓.zip的壓縮包 安裝mount數據庫 圖形化安裝 清除之前的掛載 開啟Disql服務 修改dmdba的環境變量 檢查狀態 進入數據庫 …

MySQL與Oracle視圖:深入解析與全面對比

視圖概念 視圖在 MySQL 與Oracle中本質上是一種虛擬表&#xff0c;其數據并非實際存儲&#xff0c;而是基于一個或多個基礎表的查詢結果動態生成。它像是對復雜查詢的一種封裝&#xff0c;極大地簡化了數據的查詢操作。例如&#xff0c;當我們需要頻繁從多個關聯表中獲取特定數…

uniapp通過webview套h5時使用plus調取藍牙/usb打印

安卓使用usb調取打印機 /*** 安卓usb調取打印機*param { string | bytes[] } html 傳入的打印內容*傳入一段文本或一個bytes數組* returns*/ export const printUsb (html) > {return new Promise((resolve, reject) > {if (!window.plus) return reject(new Error(&qu…

吃透 Golang 基礎:基于共享變量的并發

文章目錄 sync.Mutex 互斥鎖sync.RWMutex 讀寫鎖sync.Once 惰性初始化Goroutine 與線程動態棧Goroutine 調度GOMAXPROCSGoroutine 沒有 ID 號 上一篇文章當中我們已經系統性地回顧了在 Go 當中基于 Goroutine 和 Channel 進行并發控制的方法&#xff0c;Goroutine 指的是 Golan…

智紳科技丨如何選擇一家好的養老機構?

居家養老、社區養老和機構養老是我們在養老相關消息中常常聽到的3個詞。在地方文件中&#xff0c;居家養老和社區養老還經常被統稱為居家社區養老或 社區居家養老。那么&#xff0c;這三者之間到底有什么不同呢&#xff1f; 居家養老服務涵蓋生活照料、家政服務、康復護理、醫…

【支持向量機】SVM線性支持向量機學習算法——軟間隔最大化支持向量機

支特向量機(support vector machines, SVM)是一種二類分類模型。它的基本模型是定義在特征空間上的間隔最大的線性分類器。包含線性可分支持向量機、 線性支持向量機、非線性支持向量機。 當訓練數據近似線性可分時&#xff0c;通過軟間隔最大化學習線性分類器&#xff0c; 即為…

面試 — 預準備 — 面試前準備攻略

好記憶不如爛筆頭&#xff0c;能記下點東西&#xff0c;就記下點&#xff0c;有時間拿出來看看&#xff0c;也會發覺不一樣的感受. 只講干貨&#xff0c;不羅里吧嗦&#xff01; 作為一個軟件從業者&#xff0c;在面試前的準備工作至關重要&#xff0c;能大幅提升你的求職成功…

Oracle停庫shutdown長時間無反應

Oracle停庫shutdown長時間無反應 現象:Oracle停庫卡住,長時間沒有反應。 SQL> shutdown immediate;注:此時切記不可Ctrl+C直接取消!切記不可Ctrl+C直接取消!切記不可Ctrl+C直接取消! 檢查alert_SID.log日志看是哪些會話進程導致的: Shutting down instance (immed…

使用ZYNQ芯片和LVGL框架實現用戶高刷新UI設計系列教程(第十八講

列表部件基本上是一個采用垂直布局的矩形&#xff0c;可向其中添加按鈕和文本。 部件包含&#xff1a; LV_PART_MAIN - 主要的屬性&#xff0c;大部分是這個部件。 LV_PART_SCROLLBAR - 滾動條的屬性。 &#xff08;1&#xff09; 添加文本 lv_obj_t * lv_list_add_text(lv_o…

Android Navigation 原理解析

1. nav_graph.xml 如何生成路由表 NavGraph 解析流程與原理 關鍵技術點&#xff1a; XML 解析&#xff1a; 使用 XmlResourceParser 解析 XML 文件 遍歷所有節點&#xff08;<fragment>, <activity>, <navigation>等&#xff09; Destination 創建&#…

HarmonyOS 應用權限管控流程

HarmonyOS 應用權限管控流程詳解 一、權限管控概述 HarmonyOS 通過多層次的安全機制保護用戶數據和系統資源&#xff0c;其中應用權限管控是核心組成部分。系統通過以下機制實現權限管控&#xff1a; 應用沙箱&#xff1a;每個應用運行在獨立沙箱中&#xff0c;通過TokenID識…

Python訓練營-Day33

import torch torch.cudaimport torch# 檢查CUDA是否可用 if torch.cuda.is_available():print("CUDA可用&#xff01;")# 獲取可用的CUDA設備數量device_count torch.cuda.device_count()print(f"可用的CUDA設備數量: {device_count}")# 獲取當前使用的C…

【STM32】中斷優先級管理 NVIC

這篇文章是對 Cortex-M3 內核中斷系統 和 STM32F1 系列 NVIC(嵌套向量中斷控制器) 的解析說明。我將從結構清晰、層次分明的角度,對 NVIC 中斷優先級分組的概念和 STM32F103 的實際情況做一個系統性的總結與敘述。 參考資料: STM32F1xx官方資料:《STM32中文參考手冊V10》…

Angular2--高級特性(TODO)

1 基礎 關于Angular的基礎部分&#xff0c;幾個核心部分和框架&#xff0c;在之前都寫過了。Angular1--Hello-CSDN博客 Angular的幾個核心部分和框架&#xff1a; 模板就是組件中的template&#xff0c;對應MVC的V。 組件類就是Component類&#xff0c;對應對應MVC的C。 服…

pikachu靶場通關筆記44 SSRF關卡02-file_get_content(三種方法滲透)

目錄 一、SSRF 1、簡介 2、原理 二、file_get_contents函數 1、功能 2、參數 3、返回值 4、file_get_contents與SSRF 三、滲透實戰 1、基本探測 2、http協議 &#xff08;1&#xff09;訪問upload-labs靶場 &#xff08;2&#xff09;訪問yijuhua.txt 3、file協議…

Android 控件 - EditText 的 Hint(Hint 基本用法、Hint 進階用法、單獨設置 Hint 的大小)

一、EditText 的 Hint 1、基本介紹 在 Android 開發中&#xff0c;EditText 的 Hint 用于顯示提示文本 提示文本當用戶沒有輸入任何內容時顯示&#xff0c;輸入內容后自動消失 2、基本使用 &#xff08;1&#xff09;在 XML 布局文件中設置 在 XML 布局文件中設置 Hint …

PostgreSQL(知識片):索引關聯度indexCorrelation

索引關聯度的絕對值越大&#xff0c;說明這個索引數據越好。絕對值最大為1。 首先我們創建一個表&#xff1a;tbl_corr&#xff0c;包含列&#xff1a;col、col_asc、col_desc、col_rand、data&#xff0c;col_asc存儲順序數據&#xff0c;col_desc存儲降序數據&#xff0c;col…

React純函數和hooks原理

純函數 JS 若滿足其下條件 &#xff0c;被稱為純函數 1。確定的輸入一定產生確定的輸出 2 不產生副作用 另外redux中的reducer也要求是純函數 Fiber 架構和hooks原理 useRef 在組件的整個聲明周期內保持不變 用法&#xff1a;1綁定dom元素 或者 綁定一個類組件 因為函數式…

養老專業實訓室虛擬仿真建設方案:助力人才培養與教育教學革新

隨著我國老齡化程度加深&#xff0c;養老服務行業人才需求激增。養老專業實訓室虛擬仿真建設方案憑借虛擬仿真技術&#xff0c;為養老專業教育教學帶來革新&#xff0c;對人才培養意義重大。點擊獲取實訓室建設方案 一、構建多元化虛擬場景&#xff0c;豐富實踐教學內容 模擬居…

LangChain 提示詞工程:語法結構詳解與完整實戰指南

LangChain 提示詞工程&#xff1a;語法結構詳解與完整實戰指南 我將為您系統性地解析 LangChain 中各類提示模板的核心語法結構&#xff0c;通過清晰展示語法與對應代碼示例&#xff0c;幫助您徹底掌握提示工程的實現方法。所有示例均圍繞報幕詞生成場景展開。 在這里插入圖片…