源碼視角,vue3為什么推薦用ref,而不是reactive

ref?和?reactive?是 Vue3 中實現響應式數據的核心 API。ref?用于包裝基本數據類型,而 reactive 用于處理對象和數組。盡管?reactive?似乎更適合處理對象,但?Vue3 官方文檔更推薦使用?ref

1e81de1370834ecc8d8c3876249468b7.png

?

我的想法,ref就是比reactive好用,官方也是這么說的,不服來踩!下面我們從源碼的角度詳細討論這兩個 API,以及 Vue3 為什么推薦使用ref而不是reactive

?

ref 的內部工作原理

ref?是一個函數,它接受一個內部值并返回一個響應式且可變的引用對象。這個引用對象有一個?.value?屬性,該屬性指向內部值。

// 深響應式
export function ref(value?: unknown) {return createRef(value, false)
}// 淺響應式
export function shallowRef(value?: unknown) {return createRef(value, true)
}function createRef(rawValue: unknown, shallow: boolean) {// 如果傳入的值已經是一個 ref,則直接返回它if (isRef(rawValue)) {return rawValue}// 否則,創建一個新的 RefImpl 實例return new RefImpl(rawValue, shallow)
}class RefImpl<T> {// 存儲響應式的值。我們追蹤和更新的就是_value。(這個是重點)private _value: T// 用于存儲原始值,即未經任何響應式處理的值。(用于對比的,這塊的內容可以不看)private _rawValue: T // 用于依賴跟蹤的 Dep 類實例public dep?: Dep = undefined// 一個標記,表示這是一個 ref 實例public 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)// toRaw 用于將響應式引用轉換回原始值// toReactive 函數用于將傳入的值轉換為響應式對象。對于基本數據類型,toReactive 直接返回原始值。// 對于對象和數組,toReactive 內部會調用 reactive 來創建一個響應式代理。// 因此,對于 ref 來說,基本數據類型的值會被 RefImpl 直接包裝,而對象和數組// 會被 reactive 轉換為響應式代理,最后也會被 RefImpl 包裝。// 這樣,無論是哪種類型的數據,ref 都可以提供響應式的 value 屬性,// 使得數據變化可以被 Vue 正確追蹤和更新。// export const toReactive = (value) => isObject(value) ? reactive(value) : value}get value() {// 追蹤依賴,這樣當 ref 的值發生變化時,依賴這個 ref 的組件或副作用函數可以重新運行。trackRefValue(this)// 返回存儲的響應式值return this._value}set value(newVal) {// 判斷是否應該使用新值的直接形式(淺響應式或只讀)const useDirectValue =this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)// 如果需要,將新值轉換為非響應式原始值newVal = useDirectValue ? newVal : toRaw(newVal)// 如果新值與舊值不同,更新 _rawValue 和 _valueif (hasChanged(newVal, this._rawValue)) {this._rawValue = newValthis._value = useDirectValue ? newVal : toReactive(newVal)// 觸發依賴更新triggerRefValue(this, DirtyLevels.Dirty, newVal)}}
}

在上述代碼中,ref?函數通過?new RefImpl(value)?創建了一個新的?RefImpl?實例。這個實例包含 getter 和 setter,分別用于追蹤依賴和觸發更新。使用?ref?可以聲明任何數據類型的響應式狀態,包括對象和數組。

import { ref } from 'vue' let state = ref({ count: 0 })
state.value.count++

注意,ref核心是返回響應式且可變的引用對象,而reactive核心是返回的是響應式代理,這是兩者本質上的核心區別,也就導致了ref優于reactive,我們接著看下reactive源碼實現。

reactive 的內部工作原理

reactive?是一個函數,它接受一個對象并返回該對象的響應式代理,也就是?Proxy

function reactive(target) {if (target && target.__v_isReactive) {return target}return createReactiveObject(target,false,mutableHandlers,mutableCollectionHandlers,reactiveMap)
}function createReactiveObject(target,isReadonly,baseHandlers,collectionHandlers,proxyMap
) {if (!isObject(target)) {return target}const existingProxy = proxyMap.get(target)if (existingProxy) {return existingProxy}const proxy = new Proxy(target, baseHandlers)proxyMap.set(target, proxy)return proxy
}

reactive的源碼相對就簡單多了,reactive?通過?new Proxy(target, baseHandlers)?創建了一個代理。這個代理會攔截對目標對象的操作,從而實現響應式。

import { reactive } from 'vue' let state = reactive({ count: 0 })
state.count++

到這里我們可以看出?ref?和?reactive?在聲明數據的響應式狀態上,底層原理是不一樣的。ref?采用?RefImpl對象實例,reactive采用Proxy代理對象。

ref 更深入的理解

當你使用?new RefImpl(value)?創建一個?RefImpl?實例時,這個實例大致上會包含以下幾部分:

  1. 內部值:實例存儲了傳遞給構造函數的初始值。
  2. 依賴收集:實例需要跟蹤所有依賴于它的效果(effect),例如計算屬性或者副作用函數。這通常通過一個依賴列表或者集合來實現。
  3. 觸發更新:當實例的值發生變化時,它需要通知所有依賴于它的效果,以便它們可以重新計算或執行。

RefImpl 類似于發布-訂閱模式的設計,以下是一個簡化的?RefImpl?類的偽代碼實現,展示這個實現過程:

class Dep {constructor() {this.subscribers = new Set();}depend() {if (activeEffect) {this.subscribers.add(activeEffect);}}notify() {this.subscribers.forEach(effect => effect());}
}let activeEffect = null;function watchEffect(effect) {activeEffect = effect;effect();activeEffect = null;
}class RefImpl {constructor(value) {this._value = value;this.dep = new Dep();}get value() {// 當獲取值時,進行依賴收集this.dep.depend();return this._value;}set value(newValue) {if (newValue !== this._value) {this._value = newValue;// 值改變時,觸發更新this.dep.notify();}}
}// 使用示例
let count = new RefImpl(0);watchEffect(() => {console.log(`The count is: ${count.value}`); // 訂閱變化
});count.value++; // 修改值,觸發通知,重新執行watchEffect中的函數

Dep 類負責管理一個依賴列表,并提供依賴收集和通知更新的功能。RefImpl 類包含一個內部值 _value 和一個 Dep 實例。當 value 被訪問時,通過 get 方法進行依賴收集;當 value 被賦予新值時,通過 set 方法觸發更新。

?

ref?和?reactive?盡管兩者在內部實現上有所不同,但它們都能滿足我們對于聲明響應式變量的要求,但是?reactive?卻存在一定的局限性。

reactive 的局限性

在 Vue3 中,reactive?API 通過?Proxy?實現了一種響應式數據的方法,盡管這種方法在性能上比 Vue2 有所提升,但?Proxy?的局限性也導致了?reactive?的局限性,這些局限性可能會影響開發者的使用體驗。

僅對引用數據類型有效

reactive?主要適用于對象,包括數組和一些集合類型(如?Map?和?Set)。對于基礎數據類型(如?stringnumber?和?boolean),reactive?是無效的。這意味著如果你嘗試使用?reactive?來處理這些基礎數據類型,將會得到一個非響應式的對象。

import { reactive } from 'vue';
const state = reactive({ count: 0 });

使用不當會失去響應

  1. 直接賦值對象:如果直接將一個響應式對象賦值給另一個變量,將會失去響應性。這是因為 reactive 返回的是對象本身,而不僅僅是代理。

    import { reactive } from 'vue';let state = reactive({ count: 0 });
    state = { count: 1 }; // 失去響應性
    
  2. 直接替換響應式對象:同樣,直接替換一個響應式對象也會導致失去響應性。

    import { reactive } from 'vue';let state = reactive({ count: 0 });
    state = reactive({ count: 1 }); // 失去響應性
    
  3. 直接解構對象:在解構響應式對象時,如果直接解構對象屬性,將會得到一個非響應式的變量。

    const state = reactive({ count: 0 });let { count } = state;
    count++; // count 仍然是 0
    

    解決這個問題,需要使用?toRefs?函數來將響應式對象轉換為?ref?對象。

    import { toRefs } from 'vue';const state = reactive({ count: 0 });
    let { count } = toRefs(state);
    count++; // count 現在是 1
    
  4. 將響應式對象的屬性賦值給變量:如果將響應式對象的屬性賦值給一個變量,這個變量的值將不會是響應式的。

    let state = reactive({ count: 0 })let count = state.count
    count++  // count 仍然是 0
    console.log(state.count)
    

使用?reactive?聲明響應式變量的確存在一些不便之處,尤其是對于喜歡使用解構賦值的開發者而言。這些局限性可能會導致意外的行為,因此在使用?reactive?時需要格外注意。相比之下,ref?API 提供了一種更靈活和統一的方式來處理響應式數據。

為什么推薦使用 ref ?

ref()它為響應式編程提供了一種統一的解決方案,適用于所有類型的數據,包括基本數據類型和復雜對象。以下是推薦使用 ref 的幾個關鍵原因:

統一性

ref?的核心優勢之一是它的統一性。它提供了一種簡單、一致的方式來處理所有類型的數據,無論是數字、字符串、對象還是數組。這種統一性極大地簡化了開發者的代碼,減少了在不同數據類型之間切換時的復雜性。

import { ref } from 'vue';let num = ref(0);
let str = ref('Hello');
let obj = ref({ count: 0 });// 修改基本數據類型
num.value++;
str.value += ' World';// 修改對象
obj.value.count++;

深層響應性

ref?支持深層響應性,這意味著它可以追蹤和更新嵌套對象和數組中的變化。這種特性使得?ref?非常適合處理復雜的數據結構,如對象和數組。

import { ref } from 'vue';let obj = ref({user: {name: 'xiaoming',details: {age: 18}}
});// 修改嵌套對象
obj.value.user.details.age++;

當然,為了減少大型不可變數據的響應式開銷,也可以通過使用shallowRef來放棄深層響應性。

let shallowObj = shallowRef({ details: { age: 18, }, 
});

靈活性

ref?提供了高度的靈活性,尤其在處理普通賦值方面。這種靈活性使得 ref 在開發中的使用更加方便,特別是在進行復雜的數據操作時。

import { ref } from 'vue';let state = ref({count: 0,name: 'Vue'
});// 替換整個對象
state.value = {count: 10,name: 'Vue 4'
};
// 修改對象內的屬性
state.value.count = 20;
state.value.name = 'Vue 5';
// 添加新的屬性
state.value.newProperty = 'New Property';
// 刪除屬性
delete state.value.newProperty;
// 使用解構更新屬性(注意要保持響應性)
let { count, name } = state.value;
state.value = { count: count + 1, name };
// 復雜操作,例如根據條件更新屬性
if (someCondition) {state.value = {...state.value,name: 'Updated Name'};
}
console.log(state.value)

總結

ref?在 Vue3 中提供了一種更統一、靈活的響應式解決方案,還能避免了?reactive?的某些局限性。希望這篇文章對你有所幫助,有所借鑒。大家怎么認為呢,評論區我們一起討論下!

?

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

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

相關文章

Java 中對包含關系的判斷

本文將為您詳細講解 Java 中對包含關系的判斷&#xff0c;包括數組、字符串等&#xff0c;并提供相應的代碼例子。 1. 數組包含關系判斷 在 Java 中&#xff0c;數組包含關系判斷通常使用循環來實現。以下是幾種常見的判斷方法&#xff1a; 示例 1&#xff1a;使用 for…

Unity曲柄滑塊四桿機構運動計算

一、運動效果 二、機構的介紹 曲柄長度&#xff1a;a&#xff0c;線段AB長度 連桿長度&#xff1a;b&#xff0c;線段BC長度 偏心距離&#xff1a;e&#xff0c;滑塊軌跡與曲柄中心點A的垂直距離 三、已知點A點B和e的值&#xff0c;計算C點的位置 1、計算s的值 var h math.…

通過多進程并發方式(fork)實現服務器(注意要回收子進程)

以下內容為視頻學習記錄。 1、父進程accept后返回的文件描述符為cfd以及用于創建連接的lfd; 調用fork()創建子進程后&#xff0c;子進程繼承cfd,lfd&#xff0c;通過該cfd與連接過來的客戶端通信,lfd對子進程來說沒用&#xff0c;可以直接close(lfd); 對于父進程來說&#x…

雙非二本找實習前的準備day4

學習目標&#xff1a; 每天2-3到簡單sql&#xff08;刷完即止&#xff09;&#xff0c;每天復習代碼隨想錄上的題目3道算法&#xff08;時間充足可以繼續&#xff09;&#xff0c;背誦的八股的問題也在這里記錄了 今日碎碎念&#xff1a; 1&#xff09;偶爾還是貪玩游戲&…

Vue中的計算屬性和方法有什么區別?

Vue.js是一款流行的JavaScript前端框架&#xff0c;提供了豐富的功能和便捷的開發方式。在Vue中&#xff0c;計算屬性和方法是常用的兩種方式來處理數據和邏輯。但它們之間存在一些區別&#xff0c;本文將詳細介紹Vue中計算屬性和方法的區別&#xff0c;并通過示例代碼加深理解…

183896-00-6,Biotin-C3-PEG3-C3-NH2,可以選擇性降解靶蛋白

您好&#xff0c;歡迎來到新研之家 文章關鍵詞&#xff1a;183896-00-6&#xff0c;Biotin-C3-PEG3-C3-NH2&#xff0c;Biotin-C3-PEG3-C3-amine&#xff0c;生物素-C3-PEG3-C3-胺 一、基本信息 【產品簡介】&#xff1a;Biotin-PEG3-C3-NH2是一種PROTAC linker&#xff0c;…

381. 有線電視網絡(網絡流,最小割,《算法競賽進階指南》)

381. 有線電視網絡 - AcWing題庫 給定一張 n 個點 m 條邊的無向圖&#xff0c;求最少去掉多少個點&#xff0c;可以使圖不連通。 如果不管去掉多少個點&#xff0c;都無法使原圖不連通&#xff0c;則直接返回 n。 輸入格式 輸入包含多組測試數據。 每組數據占一行&#xf…

Python推導式大全與實戰:精通列表、字典、集合和生成器推導式【第115篇—python:推導式】

Python推導式大全與實戰&#xff1a;精通列表、字典、集合和生成器推導式 Python語言以其簡潔、優雅的語法而聞名&#xff0c;其中推導式是其獨特之處之一。推導式是一種在一行代碼中構建數據結構的強大方式&#xff0c;它涵蓋了列表、字典、集合和生成器。本篇博客將全面介紹…

YOLOv8實例分割實戰:ONNX模型轉換及TensorRT部署

課程鏈接&#xff1a;https://edu.csdn.net/course/detail/39320 PyTorch版的YOLOv8支持高性能的實時實例分割。 TensorRT是針對英偉達GPU的加速工具。 ONNX &#xff08;Open Neural Network Exchange&#xff09; 作為一個開放的網絡模型中間表示&#xff08;IR&#xff0…

Redis命令大全

通用命令 KEYS pattern&#xff1a;查找所有符合給定模式&#xff08;pattern&#xff09;的 key。EXISTS key&#xff1a;檢查指定 key 是否存在。TYPE key&#xff1a;返回指定 key 的數據類型。DEL key [key …]&#xff1a;刪除指定的 key。RENAME key newkey&#xff1a;…

spring boot 修復 Spring Framework URL解析不當漏洞(CVE-2024-22243)

漏洞描述 當應用程序使用UriComponentsBuilder來解析外部提供的URL&#xff08;如通過查詢參數&#xff09;并對解析的URL的主機執行驗證檢查時可能容易受到Open重定向攻擊和SSRF攻擊&#xff0c;導致網絡釣魚和內部網絡探測等。 受影響產品或系統 6.1.0 < Spring Framew…

Vue項目的快速搭建

Vue項目的快速搭建 一、下載并安裝node.js二、安裝Vue腳手架三、創建vue項目四、項目啟動五、VS Code下載安裝 一、下載并安裝node.js 首先確保已經安裝了Node.js。如果沒有安裝&#xff0c;可以去官網&#xff08;https://nodejs.org/&#xff09;下載并安裝最新版本的Node.j…

基于STC12C5A60S2系列1T 8051單片機的TM1638鍵盤數碼管模塊的數碼管顯示應用

基于STC12C5A60S2系列1T 8051單片機的TM1638鍵盤數碼管模塊的數碼管顯示應用 STC12C5A60S2系列1T 8051單片機管腳圖STC12C5A60S2系列1T 8051單片機I/O口各種不同工作模式及配置STC12C5A60S2系列1T 8051單片機I/O口各種不同工作模式介紹TM1638鍵盤數碼管模塊概述TM1638鍵盤數碼管…

mybatis-傳遞參數的方式

mybatis 傳遞參數的7種方法 在實際開發過程中&#xff0c;增刪改查操作都要涉及到請求參數的傳遞&#xff0c;今天這節就集中講下在mybatis中傳遞參數的7中方法 單個參數的傳遞很簡單沒有什么好將的&#xff0c;這里主要說下多個參數的傳遞 1、第一種方式 匿名參數 順序傳遞…

[electron]窗口 BrowserWindow

優雅的顯示窗口 const {app, BrowserWindow} require(electron);function createMainwindow(){const mainwindow new BrowserWindow({x: 300,y: 400,width: 600,height: 600,});mainwindow.loadFile(index.html); }app.on(ready, ()>{createMainwindow(); });對于這樣的代…

前端發起請求,后端模型需處理很久,怎樣設置前端直接完成請求響應,后端計算完在返回結果給前端?

在這種情況下&#xff0c;可以采用異步處理的方式來解決。具體步驟如下&#xff1a; 前端發起請求&#xff1a;前端向后端發送請求&#xff0c;但是不等待后端處理完成而是立即得到響應。 后端異步處理&#xff1a;后端接收到請求后&#xff0c;不立即進行處理&#xff0c;而是…

Codeforces Round 886 (Div. 4)----->E. Cardboard for Pictures

一&#xff0c;思路&#xff1a; 這題我們可以通過二分 w來直接得到答案&#xff0c;時間復雜度是nlogn的級別&#xff0c;但是這里有個很坑的地方&#xff0c;就是假如你用二分做&#xff0c;會面臨報 long long 的問題&#xff0c;但是問題不大&#xff0c;直接用 unsigned …

題目:金三銀四求職季:如何脫穎而出

題目&#xff1a;金三銀四求職季&#xff1a;如何脫穎而出 引言&#xff1a; 隨著春天的腳步漸近&#xff0c;對于許多程序員來說&#xff0c;一年中最繁忙、最重要的面試季節也隨之而來。金三銀四&#xff0c;即三月和四月&#xff0c;被廣大程序員視為求職的黃金時期。在這兩…

藍橋杯倒計時 41天 - KMP 算法

KMP算法 KMP算法是一種字符串匹配算法&#xff0c;用于匹配模式串P在文本串S中出現的所有位置。 例如S“ababac&#xff0c;P“aba”&#xff0c;那么出現的所有位置是13。 在初學KMP時&#xff0c;我們只需要記住和學會使用模板即可&#xff0c;對其原理只需簡單理解&#xff…

用Socks5代理游戲,繞過“網絡海關”去探險

1. 出海大冒險的開始 在游戲世界&#xff0c;就像在現實生活中一樣&#xff0c;有時我們需要越過海洋去探索未知的世界。但是&#xff0c;網絡上也有一些“海關”&#xff0c;限制我們訪問某些網站或游戲服務器。這就是我們今天要克服的挑戰&#xff01; 2. Socks5代理&#xf…