框架的源碼理解——V3中的ref和reactive

最近在研究各個框架的源碼,從源碼角度去理解 vue3 的 reactive 和 ref API,記錄下研究的成果

reactive

首先,reactive() 的參數必須是一個對象,返回值是一個 Proxy 對象,具有響應性。如果參數不是對象類型,會提示:value cannot be made reactive。
多次對同一個對象使用 reactive 進行代理,返回的是相同的代理對象,也就是說使用的是緩存的值。而且,取值時直接讀取屬性就行,不需要加 .value 。
例子:

import { reactive } from 'vue'
const state = reactive({ count: 0 })
console.log(state.count) // 0const name = reactive('hh')
console.log('name', name) // warn: value cannot be made reactive: hhconst raw = {}
const proxy = reactive(raw)
console.log(proxy === raw) // false
// calling reactive() on the same object returns the same proxy
console.log(reactive(raw) === proxy) // true
// calling reactive() on a proxy returns itself
console.log(reactive(proxy) === proxy) // true

接下來說下 reactive 的局限性。
首先,參數只支持 object 類型 (比如 objects, arrays, Map, Set),不支持基礎數據類型,比如string, number 或boolean;
其次,對變量重新賦值會丟失響應性,比如:

let state = reactive({ count: 0 })
// the above reference ({ count: 0 }) is no longer being tracked
// (reactivity connection is lost!)
state = reactive({ count: 1 })

而且,解構賦值容易丟失響應性:

const state = reactive({ count: 0 })// count is disconnected from state.count when destructured.
let { count } = state
// does not affect original state
count++

這種情況下,我們可以使用 toRefs 函數來將響應式對象轉換為 ref 對象

import { toRefs } from 'vue';const state = reactive({ count: 0 });
let { count } = toRefs(state);
count++; // count 現在是 1

ref

再來看下 ref() 。reactive 和 ref 都是聲明響應式變量的寫法,但是,ref 的參數既可以是基本數據類型的值,也可以是對象,很自由!這就是為什么我們在開發時更推薦使用 vue3 的 ref 的原因了。
而且,ref 聲明的變量在取值時必須加上 .value,而在 template 調用時中不加。
例子:
再來看下 ref() 。reactive 和 ref 都是聲明響應式變量的寫法,但是,ref 的參數既可以是基本數據類型的值,也可以是對象,很自由!這就是為什么我們在開發時更推薦使用 vue3 的 ref 的原因了。
而且,ref 聲明的變量在取值時必須加上 .value,而在 template 調用時中不加。
例子:

const {ref, effect} = Vueconst name = ref('張三')
console.log('name', name.value) // name 張三const state = ref({ count: 0 })
console.log('state', state.value.count) // state 0

ref 源碼

深入源碼看下為什么。

ref() 中調用的是 createRef(value, false),在這個函數中,首先判斷屬性 __v_isRef 是否為 true,為 true 說明是 Ref 類型的值,直接返回;否則,返回的是 RefImpl 類的實例。

類的 get 和 set

再來看 RefImpl 類,重點是類中定義了 get 函數和 set 函數。當我們對類實例的 value 屬性取值和賦值時,就會觸發這兩個函數。

// ref.tsexport function ref(value?: unknown) {return createRef(value, false)
}function createRef(rawValue: unknown, shallow: boolean) {// 判斷屬性 __v_isRef 是否為 true,為 true 說明是 Ref 類型的值,直接返回if (isRef(rawValue)) {return rawValue}return new RefImpl(rawValue, shallow)
}export function isRef(r: any): r is Ref {return !!(r && r.__v_isRef === true)
}class RefImpl<T> {private _value: Tprivate _rawValue: T// 依賴項public dep?: Dep = undefined// 屬性 __v_isRef 設置為 truepublic readonly __v_isRef = trueconstructor(value: T, public readonly __v_isShallow: boolean) {this._rawValue = __v_isShallow ? value : toRaw(value)this._value = __v_isShallow ? value : toReactive(value)}get value() {// 依賴收集trackRefValue(this)// 返回值return this._value}set value(newVal) {const useDirectValue =this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)newVal = useDirectValue ? newVal : toRaw(newVal)if (hasChanged(newVal, this._rawValue)) {this._rawValue = newValthis._value = useDirectValue ? newVal : toReactive(newVal)triggerRefValue(this, newVal)}}
}

舉個例子理解下類中的 get 和 set 函數:

class RefImpl {// ref實例的getter行為get value () {console.log('get');return '111'}// ref實例的setter行為set value (val) {console.log('set');}
}const ref = new RefImpl()ref.value = '123'
ref.value

這里定義了 RefImpl 類,當我們對 ref.value 賦值時,會打印 set;當我們調用 ref.value 時,會打印 get。因此,我們不難理解為什么 Vue3 的 ref() 要加上 .value 了,因為也是使用了類中的 getter 和 setter 的寫法。
此外,ref() 最終的返回值是 this._value,我們再來看下這部分的代碼。這里是判斷屬性 __v_isShallow 是否為 true,為true 則直接返回,否則經過 toReactive() 處理下再返回。

this._value = __v_isShallow ? value : toReactive(value)

toReactive()

看下這個函數發生了什么。可以看到,如果參數是對象類型,則使用 reactive() 處理一下并返回;否則直接返回這個參數。
而 reactive() 中,我們是返回一個對象的 Proxy 對象,這個 Proxy 對象具有響應性,可以監聽到我們對對象屬性的讀取和修改。值得一提的是,這里的 reactive() 正是 上面說到的聲明響應性變量的 reactive() !也就是說,ref 的底層也用到了 reactive() ,二者是相通的,只不過 ref 多包裝了一層,支持了基本數據類型的值。

// reactive.ts/*** Returns a reactive proxy of the given value (if possible).** If the given value is not an object, the original value itself is returned.** @param value - The value for which a reactive proxy shall be created.*/
export const toReactive = <T extends unknown>(value: T): T =>isObject(value) ? reactive(value) : value/*** Returns a reactive proxy of the object.** The reactive conversion is "deep": it affects all nested properties. A* reactive object also deeply unwraps any properties that are refs while* maintaining reactivity.** @example* ```js* const obj = reactive({ count: 0 })* ```** @param target - The source object.* @see {@link https://vuejs.org/api/reactivity-core.html#reactive}*/
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {// if trying to observe a readonly proxy, return the readonly version.if (isReadonly(target)) {return target}return createReactiveObject(target,false,mutableHandlers,mutableCollectionHandlers,reactiveMap)
}

createReactiveObject()

看下響應性是如何實現的。

首先,在 createReactiveObject() 函數中,如果傳參 target 是非對象類型的,會提示并直接返回,我們之前的例子中也觀察到這種現象了;
其次,判斷 target 是否是 Proxy 或者已經存在哈希表 proxyMap 中,如果是直接返回;

最后,如果傳參只是一個普通的對象,我們需要使用 new Proxy() 將其轉化為一個 Proxy 對象,我們知道在 Vue3 中響應性的實現正是通過 Proxy 去實現的。生成 Proxy 對象后,存入 proxyMap 中,并返回該 Proxy 對象即可。

function createReactiveObject(target: Target,isReadonly: boolean,baseHandlers: ProxyHandler<any>,collectionHandlers: ProxyHandler<any>,proxyMap: WeakMap<Target, any>
) {if (!isObject(target)) {if (__DEV__) {console.warn(`value cannot be made reactive: ${String(target)}`)}return target}// target is already a Proxy, return it.// exception: calling readonly() on a reactive objectif (target[ReactiveFlags.RAW] &&!(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {return target}// target already has corresponding Proxyconst existingProxy = proxyMap.get(target)if (existingProxy) {return existingProxy}// only specific value types can be observed.const targetType = getTargetType(target)if (targetType === TargetType.INVALID) {return target}const proxy = new Proxy(target,targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers)proxyMap.set(target, proxy)return proxy
}

小結:
createReactiveObject 函數,即 reactive 函數,最終是將傳參的對象轉化為一個 Proxy 對象并返回,而 Vue3 中響應性的實現正是通過 Proxy 去實現的。

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

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

相關文章

能源數字化轉型關鍵引擎:Profinet轉Modbus TCP網關驅動設備協同升級

在工業自動化的世界中&#xff0c;ModbusTCP和Profinet是兩個非常重要的通訊協議。ModbusTCP以其開放性和易用性&#xff0c;被廣泛應用于各種工業設備中&#xff1b;而Profinet則以其高效性和實時性&#xff0c;成為了眾多高端設備的首選。然而&#xff0c;由于這兩種協議的差…

【ant design】ant-design-vue 4.0實現主題色切換

官網&#xff1a;Ant Design Vue — An enterprise-class UI components based on Ant Design and Vue.js 我圖方便&#xff0c;直接在 app.vue 中加入的 <div class"app-content" v-bind:class"appOption.appContentClass"><a-config-provider…

一個指令,讓任意 AI 快速生成思維導圖

大家好&#xff0c;我是安仔&#xff0c;一個每天都在壓榨 AI 的躺平打工人。 今天分享一個 AI 辦公小技巧&#xff0c;讓你用一個指令讓 AI 生成思維導圖。 DeepSeek、Kimi、豆包都可以哈 &#xff5e; KimiXMind 安仔經常用 XMind 來繪制思維導圖&#xff0c;但是 AI 是沒…

便捷的批量打印工具推薦

軟件介紹 本文介紹的軟件是一款批量打印軟件&#xff0c;名為PrintConductor。 軟件功能強大 這款批量打印軟件功能極為強大&#xff0c;它不僅能夠批量打印各種不同格式的文件&#xff0c;還可以直接打印整個文件夾。 初次使用設置 第一次打開這款軟件時&#xff0c;要記…

USRP 射頻信號 采集 回放 系統

USRP 射頻信號采集回放系統 也可以叫做&#xff1a; 利用寬帶RF錄制和回放系統實現6G技術研究超寬帶射頻信號采集回放系統使用NI USRP平臺實現射頻信號錄制和回放操作演示USRP也能實現多通道寬帶信號流盤回放了&#xff01; 對于最簡單的實現方法就是使用LabVIEW進行實現 采…

MFC 調用海康相機進行軟觸發

初始化相機類文件 #pragma once #include "MvCameraControl.h" class CMvCamera { public:CMvCamera();~CMvCamera();//初始化相機int InitCamera();int SaveCurrentImage(CString filePath);//關閉相機void CloseCamera();//設置int SetEnumValue(IN const char* s…

虛擬主播肖像權保護,數字時代的法律博弈

首席數據官高鵬律師團隊 在虛擬主播行業蓬勃發展的表象之下&#xff0c;潛藏著一場關乎法律邊界的隱形戰爭。當一位虛擬偶像的3D模型被非法拆解、面部數據被批量復制&#xff0c;運營方驚訝地發現——傳統的肖像權保護體系&#xff0c;竟難以完全覆蓋這具由代碼與數據構成的“…

ArrayList-集合使用

自動擴容&#xff0c;集合的長度可以變化&#xff0c;而數組長度不變&#xff0c;集合更加靈活。 集合只能存引用數據類型&#xff0c;不能直接存基本數據類型&#xff0c;除非包裝 ArrayList會拿[]展示數據

鴻蒙ArkUI體驗:Hexo博客客戶端開發心得

最近部門也在跟進鴻蒙平臺的業務開發&#xff0c;自己主要是做 Android 開發&#xff0c;主要使用 Kotlin/Java 語言。&#xff0c;需要對新的開發平臺和開發模式進行學習&#xff0c;在業余時間開了個項目練手&#xff0c;做了個基于 Hexo 博客內容開發的App。鴻蒙主要使用Ark…

【和春筍一起學C++】(十四)指針與const

將const用于指針&#xff0c;有兩種情況&#xff1a; const int *pt; int * const pt; 目錄 1. const int *pt 2. int * const pt 3. 擴展 1. const int *pt 首先看第一種情況&#xff0c;const在int的前面&#xff0c;有如下語句&#xff1a; int peoples12&#xff1…

本地緩存更新方案探索

文章目錄 本地緩存更新方案探索1 背景2 方案探索2.1 初始化2.2 實時更新2.2.1 長輪詢2.2.1.1 client2.2.2.2 server 本地緩存更新方案探索 1 背景 大家在工作中是否遇到過某些業務數據需要頻繁使用&#xff0c;但是數據量不大的情況&#xff0c;一般就是幾十條甚至幾百條這種…

深入理解 requestIdleCallback:瀏覽器空閑時段的性能優化利器

requestIdleCallback 核心作用 requestIdleCallback 是瀏覽器提供的 API&#xff0c;用于將非關鍵任務延遲到瀏覽器空閑時段執行&#xff0c;避免阻塞用戶交互、動畫等關鍵任務&#xff0c;從而提升頁面性能體驗。 基本語法 const handle window.requestIdleCallback(callb…

51單片機——交通指示燈控制器設計

設計目標 1、設計一交通燈控制&#xff0c;控制東西方向的紅、黃、綠燈和南北方向的紅、黃、綠燈。 2、可手動控制和自動控制&#xff0c;設置兩個輸入控制開關。 手動/自動開關&#xff0c;通過P11的按鍵輸入控制 3、手動&#xff1a;設置開關P11&#xff0c;兩種情況&#x…

神經網絡語言模型(前饋神經網絡語言模型)

神經網絡語言模型 什么是神經網絡&#xff1f;神經網絡的基本結構是什么&#xff1f;輸入層隱藏層輸出層 神經網絡為什么能解決問題&#xff1f;通用近似定理為什么需要權重和偏置&#xff1f;為什么需要激活函數&#xff1f;權重是如何確定的&#xff1f;1. 窮舉2. 反向傳播主…

信息系統項目管理師高級-軟考高項案例分析備考指南(2023年案例分析)

個人筆記整理---僅供參考 計算題 案例分析里的計算題就是進度、掙值分析、預測技術。主要考査的知識點有:找關鍵路徑、求總工期、自由時差、總時差、進度壓縮資源平滑、掙值計算、預測計算。計算題是一定要拿下的&#xff0c;做計算題要保持頭腦清晰&#xff0c;認真讀題把PV、…

unordered_map和unordered的介紹和使用

目錄 unordered系列關聯式容器 unordered_map unordered_map的接口說明 unordered_map的定義方式 unordered_map接口的使用 unordered_map的容量 unordered_map的迭代器 unordered_map的元素訪問 unordered_map的查詢 unordered_map的修改操作 unordered_multimap u…

設計模式7大原則與UML類圖詳解

設計模式7大原則與UML類圖詳解 引言 &#x1f31f; 在軟件工程領域&#xff0c;設計模式和UML&#xff08;統一建模語言&#xff09;是提高代碼質量、增強系統可維護性的重要工具。設計模式提供了解決軟件設計中常見問題的通用方案&#xff0c;而UML則為我們提供了一種可視化的…

計算機視覺與深度學習 | Python實現ARIMA-LSTM時間序列預測(完整源碼和數據)

ARIMA-LSTM混合模型 1. 環境準備2. 數據生成(示例數據)3. 數據預處理4. ARIMA建模5. LSTM殘差建模6. 混合預測7. 結果可視化完整代碼說明1. **數據生成**2. **ARIMA建模**3. **LSTM殘差建模**4. **混合預測**5. **性能評估**參數調優建議擴展方向典型輸出以下是使用Python實現…

Docker部署單節點Elasticsearch

1.Docker部署單節點ES 1.前置條件 配置內核參數 echo "vm.max_map_count262144" >> /etc/sysctl.conf sysctl -w vm.max_map_count262144準備密碼 本文所有涉及密碼的配置&#xff0c;均使用通用密碼 Zzwl2024。 生產環境&#xff0c;請用密碼生成器生成20…

pe文件二進制解析(用c/c++解析一個二進制pe文件)

pe文件二進制解析 c解析pe文件控制臺版本 #include<iostream> #include<windows.h> #include<vector>/*RVA&#xff08;相對虛擬地址&#xff09;與FOA&#xff08;文件偏移地址&#xff09;的轉換1.得到 的值&#xff1a;內存地址 - ImageBase2.判斷是否位…