《WebGIS之Vue進階教程》(13)ref的實現

1 為什么需要ref

由于`proxy`只能代理`引用類型`數據(如: 對象, 數組, Set, Map...), 需要一種方式代理`普通類型`數據(String, Number, Boolean...)

設計ref主要是為了處理普通類型數據, 使普通類型數據也具有響應式

除此之外, 通過reactive代理的對象可能會出現響應丟失的情況. 使用ref可以在一定程度上解決響應丟失問題

2 初步實現

1) 包裹對象

既然`proxy`不能代理`普通類型`數據, 我們可以在`普通類型`數據的外層包裹一個對象

proxy代理包裹的對象(wrapper). 為了統一, 給包裹對象定義value屬性, 最后返回wrapper的代理對象

function ref(value) {const wrapper = {value: value,}return reactive(wrapper)
}

測試用例

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><script src="./reactive.js"></script></head><body><script>function ref(value) {const wrapper = {value: value,}return reactive(wrapper)}// count是一個proxy對象const count = ref(1)effect(() => {// 訪問proxy對象的屬性 觸發 getter 收集依賴console.log(count.value)})setTimeout(() => {count.value = 2}, 1000)</script></body>
</html>

2) 添加標識

按照上面的實現, 我們就無法區分一個代理對象是由`ref`創建, 還是由`reactive`創建, 比如下面的代碼
ref(1)
reactive({value: 1})

為了后續能夠對ref創建的代理對象自動脫ref處理, 即不用.value訪問.

考慮給ref創建的代理對象添加一個標識

示例

function ref(value) {const wrapper = {value: value,}// 給wrapper添加一個不可枚舉, 不可寫的屬性__v_isRefObject.defineProperty(wrapper, '__v_isRef', {value: true,})return reactive(wrapper)
}

在Vue3源碼中, 雖然不是按上述方式實現的, 但是可以這樣去理解

3 響應丟失問題

> 將`reactive`定義的代理對象** 賦值**給其它變量時, 會出現** 響應丟失問題** >

賦值主要有如下三種情況:

:::danger

  1. 如果將reactive定義的代理對象的屬性賦值給新的變量, 新變量會失去響應性
  2. 如果對reactive定義的代理對象進行展開操作. 展開后的變量會失去響應性
  3. 如果對reactive定義的代理對象進行解構操作. 解構后的變量會失去響應性

:::

1) 賦值操作

> 示例 >
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><script src="./reactive.js"></script></head><body><script>const obj = reactive({ foo: 1, bar: 2 })// 將reactive創建的代理對象的屬性賦值給一個新的變量foolet foo = obj.foo // foo此時就是一個普通變量, 不具備響應性effect(() => {console.log('foo不具備響應性...', foo)})foo = 2</script></body>
</html>
  • obj.foo表達式的返回值是1
  • 相當于定義了一個普通變量foo, 而普通變量是不具備響應性的

2) 展開操作

> 示例 >
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><script src="./reactive.js"></script></head><body><script>const obj = reactive({ foo: 1, bar: 2 })// 展開運算符應用在proxy對象上, 會從語法層面遍歷源對象的所有屬性// ...obj ===> foo:1, bar:2const newObj = {...obj,}// 此時的newObj是一個新的普通對象, 和obj之間不存在引用關系console.log(newObj) // {foo:1, bar:2}effect(() => {console.log('newObj沒有響應性...', newObj.foo)})// 改變newObj的屬性值, 不會觸發副作用函數的重新執行// 此時, 我們就說newObj失去了響應性newObj.foo = 2</script></body>
</html>

:::tips
說明

這里對proxy對象展開會經歷如下過程

  1. proxy對象屬于異質對象(exotic object)
  2. 當沒有自定義proxy對象的[[GetOwnProperty]]內部方法時, 會使用源對象的方法, 獲取所有屬性
  3. 調用GetValue, 獲取屬性值

參考文獻

ECMAScript規范2022-對象初始化

:::

2) 解構操作

> 示例 >
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><script src="./reactive.js"></script></head><body><script>const obj = reactive({ foo: 1, bar: 2 })// 對proxy對象進行解構操作后, foo和bar就是普通變量, 也失去了響應性let { foo, bar } = objeffect(() => {console.log('foo不具備響應性', foo)})// 給變量foo賦值, 不會觸發副作用函數重新執行foo = 2</script></body>
</html>

proxy對象解構后, foo就是一個普通變量, 也失去了跟obj的引用關系.

因此, 對foo的修改不會觸發副作用函數重新執行

4 toRef與toRefs

1) 基本使用

為了解決在賦值過程中響應丟失問題, Vue3提供了兩個API
  • toRef: 解決賦值問題
  • toRefs: 解決展開, 解構問題

使用演示

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><script src="https://unpkg.com/vue@3.2.41/dist/vue.global.js"></script></head><body><script>const { reactive, effect, toRef, toRefs } = Vue// obj是reactive創建的響應式數據(proxy代理對象)const obj = reactive({ foo: 1, bar: 2 })effect(() => {console.log('obj.foo具有響應性:', obj.foo)})// 使用toRef定義, 取代基本賦值操作 foo = obj.fooconst foo = toRef(obj, 'foo')effect(() => {console.log('foo.value具有響應性:', foo.value)})</script></body>
</html>
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><script src="https://unpkg.com/vue@3.2.41/dist/vue.global.js"></script></head><body><script>const { reactive, effect, toRef, toRefs } = Vue// obj是reactive創建的響應式數據(proxy代理對象)const obj = reactive({ foo: 1, bar: 2 })// 使用toRefs解構賦值 取代 {foo, bar} = objconst { foo, bar } = toRefs(obj)effect(() => {console.log('bar.value具有響應性', bar.value)})</script></body>
</html>

2) toRef的實現

> 基本實現 >
function toRef(obj, key) {const wrapper = {get value() {return obj[key]},set value(val) {obj[key] = val},}Object.defineProperty(wrapper, '__v_isRef', {value: true,})return wrapper
}

在Vue3中, 將wrapper抽象成了ObjectRefImpl類的實例, 大致的實現如下

class ObjectRefImpl {constructor(_obj, _key) {this._obj = _objthis._key = _keythis.__v_isRef = true}get value() {return this._obj[this._key]}set value(newVal) {this._obj[this._key] = newVal}
}function toRef(obj, key) {return new ObjectRefImpl(obj, key)
}

源碼解讀

:::info

  1. 源碼中的toRef實現了默認值的功能
  2. 源碼中的toRef對要轉換的數據做了判斷, 如果已經是ref類型就直接返回

:::

class ObjectRefImpl {// 支持默認值constructor(_object, _key, _defaultValue) {this._object = _objectthis._key = _keythis._defaultValue = _defaultValuethis.__v_isRef = true}get value() {const val = this._object[this._key]return val === undefined ? this._defaultValue : val}set value(newVal) {this._object[this._key] = newVal}
}
// 1. 支持默認值
function toRef(object, key, defaultValue) {const val = object[key]// 2. 如果要轉換的對象已經是ref類型, 直接返回//    eg: state = reactive({foo: ref(1)}) state.foo已經是ref類型, 直接返回ref(1)return isRef(val) ? val : new ObjectRefImpl(object, key, defaultValue)
}

測試用例

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><script src="./reactive.js"></script></head><body><script>// obj是響應式數據const obj = reactive({ foo: 1, bar: 2 })const foo = toRef(obj, 'foo')effect(() => {console.log('foo.value具備響應性:', foo.value)})</script></body>
</html>

3) toRefs的實現

> 基本實現 >
function toRefs(obj) {const ret = {}for (const key in obj) {ret[key] = toRef(obj, key)}return ret
}

原碼解讀

:::info

  1. 源碼中對obj的類型做了判斷
    1. 如果不是reactive類型的對象, 提示警告
    2. 支持代理是數組的情況

:::

function toRefs(object) {// 如果傳入的對象不具備響應性, 提示警告if (!isProxy(object)) {console.warn(`toRefs() expects a reactive object but received a plain one.`)}// 支持代理是數組的情況//   - 對象的情況: toRefs(reactive({foo: 1, bar: 2})) => {foo: ref(1), bar: ref(2)}//   - 數組的情況: toRefs(reactive(['foo', 'bar'])) => [ref('foo'), ref('bar')]const ret = isArray(object) ? new Array(object.length) : {}for (const key in object) {ret[key] = toRef(object, key)}return ret
}

測試用例

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><script src="./reactive.js"></script></head><body><script>// obj是響應式數據const obj = reactive({ foo: 1, bar: 2 })// 解構賦值const { foo, bar } = toRefs(obj)effect(() => {console.log('foo.value具備響應性:', foo.value)})</script></body>
</html>

5 自動脫ref

1) 什么是自動脫ref

> 所謂自動脫ref, 就是不寫`.value` >

對于ref類型數據, 每次在訪問時, 需要加.value才能觸發響應式.

但是這樣做無疑增加了心智負擔, 尤其是在寫模板時, 不夠優雅

為此, Vue3提供一個API: proxyRefs

對傳入的ref類型對象進行代理, 返回proxy對象

個人理解: 有點類似toRefs的逆操作??

2) 基本使用

> 使用演示 >
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><script src="./vue.js"></script></head><body><script>const { ref, proxyRefs, effect } = Vueconst count = ref(0)// 1.模擬setup的返回對象// setup函數返回的對象會經過proxyRefs處理// 這樣在模板中就不用寫.value了const setup = proxyRefs({count,})// 2.模擬頁面渲染effect(() => {console.log('不用通過.value訪問', setup.count)})</script></body>
</html>

3) proxyRefs的實現

> 基本實現 >
function proxyRefs(objectWithRefs) {return new Proxy(objectWithRefs, {get(target, key, receiver) {// 使用Reflect讀取target[key]const obj = Reflect.get(target, key, receiver)// 如果obj是ref類型, 返回obj.value; 否則, 直接返回return obj.__v_isRef ? obj.value : obj},set(target, key, newVal, receiver) {const obj = target[key]if (obj.__v_isRef) {obj.value = newValreturn obj}return Reflect.set(target, key, newVal, receiver)},})
}

源碼解讀

:::info

  1. 源碼對傳入參數加強了判斷
    1. 如果objectWithRefs已經是reactive類型, 就直接使用
  2. 源碼按功能進一步細化, 可讀性更高
    1. unref函數可以復用: 如果是ref返回.value; 否則直接返回
    2. 將proxy的handler提取成shallowUnwrapHandlers函數
    3. 在set時, 加入了新舊值類型的判斷, 更嚴謹

:::

function unref(ref) {return isRef(ref) ? ref.value : ref
}
const shallowUnwrapHandlers = {get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),set: (target, key, value, receiver) => {const oldValue = target[key]if (isRef(oldValue) && !isRef(value)) {oldValue.value = valuereturn true} else {return Reflect.set(target, key, value, receiver)}},
}
function proxyRefs(objectWithRefs) {return isReactive(objectWithRefs)? objectWithRefs: new Proxy(objectWithRefs, shallowUnwrapHandlers)
}

6 改造

按照vue的源碼進行改造
function isObject(val) {return typeof val === 'object' && val !== null
}
function toReactive(value) {return isObject(value) ? reactive(value) : value
}
class RefImpl {constructor(value) {this.dep = new Set()this._rawValue = valuethis.__v_isRef = truethis._value = toReactive(value)}get value() {trackEffects(this.dep)return this._value}set value(newValue) {if (this._rawValue != newValue) {this._rawValue = newValuethis._value = toReactive(newValue)triggerEffects(this.dep)}}
}
function ref(val) {return new RefImpl(val)
}

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

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

相關文章

Redis 緩存并發問題深度解析:擊穿、雪崩與穿透防治指南

Redis-緩存并發 引言&#xff1a;緩存&#xff0c;高性能架構的基石與并發挑戰一、 緩存擊穿&#xff1a;熱點 Key 失效引發的“單點風暴”1.1 什么是緩存擊穿&#xff1f;1.2 緩存擊穿的風險1.3 緩存擊穿的解決方案1.3.1 互斥鎖&#xff08;Mutex Lock&#xff09;/ 分布式鎖 …

Python 數據智能實戰 (4):智能用戶分群 - 融合行為

寫在前面 —— 超越 RFM 標簽,結合用戶行為與 LLM 文本洞察,實現更精準、更立體的客戶細分 歡迎回來!在前面的學習中,我們已經為 Python 數據智能工具箱添置了與大語言模型 (LLM) 交互的能力,特別是掌握了如何利用 LLM 將非結構化的文本信息轉化為包含深層語義的數值向量…

FreeMarker語法深度解析與Node.js集成實踐指南

一、FreeMarker核心語法體系 1.1 基礎模板結構 <#-- 注釋語法 --> ${expression} <#-- 輸出表達式 --> <#directive paramvalue> <#-- 指令語法 -->1.2 數據類型處理 標量類型深度處理&#xff1a; <#assign num 123.45?floor> <#--…

【計算機視覺】目標檢測:深度解析YOLOv5:下一代實時目標檢測框架實戰指南

深度解析YOLOv5&#xff1a;下一代實時目標檢測框架實戰指南 技術演進與架構設計YOLO系列發展脈絡YOLOv5核心架構1. 骨干網絡&#xff08;Backbone&#xff09;2. 特征融合&#xff08;Neck&#xff09;3. 檢測頭&#xff08;Head&#xff09; 環境配置與快速開始硬件要求建議詳…

STM32 定時器TIM

定時器基礎知識 定時器就是用來定時的機器&#xff0c;是存在于STM32單片機中的一個外設。STM32總共有8個定時器&#xff0c;分別是2個高級定時器(TIM1、TIM8)&#xff0c;4個通用定時器(TIM2、TIM3、TIM4、TIM5)和2個基本定時器(TIM6、TIM7)&#xff0c;如下圖所示: STM32F1…

OpenObserve API Usage Guide for Log Management

OpenObserve API Usage Guide for Audit Log Management 1. 概述 1.1 目標 本文檔旨在詳細介紹 OpenObserve 的 API 使用方法&#xff0c;幫助用戶通過 API 實現日志管理功能&#xff0c;包括日志攝入、查詢、模糊匹配&#xff08;類似 SQL 的 LIKE&#xff09;、stream 管理…

消防崗位技能競賽流程方案策劃

一、比賽目的&#xff1a; 為大力倡導“11.9”全國消防安全活動月&#xff0c;緊緊圍繞“人人參與消防&#xff0c;共創平安和諧”的活動主題&#xff0c;結合公司實際情況&#xff0c;特開展一次消防技能競賽活動。開展一場比思想、比工作作風、比消防業務技能、比業余文化生…

DAY9-USF4.0技術文檔筆記

目錄 1.概述 2.參考協議標準 3.術語與定義 4.引言 5.UFS架構 6.UFS電氣特性&#xff1a;時鐘、復位、信號與電源 7.復位、加電升壓和斷電降壓 8. M-PHY 9.UniPro 10.UTP 11.SCSI 12.UFS安全 13.UFS功能描述 14.描述符、標志與屬性 15.UFS機械標準 SCSI 查詢命令 1.重要產品…

安裝kubernetes 1.33版本

一、環境準備 1、內核升級 #升級內核&#xff1a; yum -y install kernel-ml-5.10.3-1.el7.elrepo.x86_64.rpm kernel-ml-devel-5.10.3-1.el7.elrepo.x86_64.rpm# 查詢可用內核版本 # awk -F\ $1"menuentry " {print i " : " $2} /etc/grub2.cfg# 調整默…

【IPMV】圖像處理與機器視覺:Lec8 Image Pyramid 圖像金字塔

【IPMV】圖像處理與機器視覺 本系列為2025年同濟大學自動化專業**圖像處理與機器視覺**課程筆記 Lecturer: Rui Fan、Yanchao Dong Lec0 Course Description Lec3 Perspective Transformation Lec7 Image Filtering Lec8 Image Pyramid 持續更新中 文章目錄 【IPMV】圖像處…

產品經理.產品設計.產品設計工具

一、 產品經理常用工具 1. 業務流程圖---系統流程圖 業務流程圖&#xff0c;面向用戶調研&#xff0c;描述業務的流轉和數據的處理要求&#xff0c;跟用戶和業務方確認&#xff1b;---業務角色的泳道流程圖。 系統流程圖&#xff0c;面向產品需求設計&#xff0c; prd系描述各…

6軸、智能、低功耗慣性測量單元BMI270及其OIS接口

BOSCH慣性傳感器IMUs 芯片代碼 通過00寄存器讀回的芯片編碼可以判斷芯片型號,BMI270為(0x24) &#xff0c;如不是該值&#xff0c;則說明不是BMI270。 型號芯片代碼BMI085CHIP_ID ( 0x1F)BMI088CHIP_ID ( 0x1E)BMI160CHIP_ID (0xD1)BMI270CHIP_ID (0x24)BMI323CHIP_ID (0x004…

【文獻速遞】鄰位連接技術(PLA)在細胞器相互作用中的應用

在神經科學研究領域&#xff0c;細胞死亡機制一直是關注的重點&#xff0c;尤其是與神經退行性疾病相關的細胞死亡形式。荷蘭格羅寧根大學的研究人員在2025年發表了“Regulation of calcium signaling prevents neuronal death mediated by NIST DEP in xenoferroptotic cell d…

六.割草機技術總結--6.RTK定位精度分析

六.割草機技術總結–6.RTK定位精度分析 6.1 1cm+1ppm 中的ppm是什么意思? 精度 RTK 位置精度(在 RTK 時)1 cm + 1 ppm ( 水 平 ) 1 . 5 cm + 1 ppm ( 垂 直 ),其中的ppm是什么意思? 在RTK(實時動態定位)技術中,ppm表示 Parts Per Million(百萬分之一),是一種與距離…

MCP的基礎知識

一、了解MCP的基礎知識 1.函數調用Function Calling Function Calling是openai在2023年推出的一個非常重要的概念&#xff1a;Function Calling&#xff08;函數調用&#xff09;本質上就是提供了大模型與外部系統的交互能力&#xff0c;類似于給大模型安裝了一個“外掛工具箱…

量化交易之數學與統計學基礎2.4——線性代數與矩陣運算 | 矩陣分解

量化交易之數學與統計學基礎2.4——線性代數與矩陣運算 | 矩陣分解 第二部分&#xff1a;線性代數與矩陣運算 第4節&#xff1a;矩陣分解&#xff1a;奇異值分解&#xff08;SVD&#xff09;在數據壓縮和風險分解的應用 一、奇異值分解&#xff08;SVD&#xff09;基礎&#xf…

極簡主義在 UI 設計中的應用與實踐:打造簡潔高效界面

極簡主義理念&#xff1a;簡潔不簡單? 極簡主義起源于 20 世紀初的包豪斯運動&#xff0c;它不僅是一種設計風格&#xff0c;更代表著一種生活態度與價值觀。其核心理念 “少即是多”&#xff0c;并非簡單地削減元素&#xff0c;而是在精簡中追求極致&#xff0c;將設計簡化到…

2025年“深圳杯”數學建模挑戰賽C題-分布式能源接入配電網的風險分析

布式能源接入配電網的風險分析 小驢數模 背景知識&#xff1a; 隨著我國雙碳目標的推進&#xff0c;可再生分布式能源在配電網中的大規模應用不可避免&#xff0c;這對傳統配電網運行提出挑戰。為了量化分析配電網中接入分布式能源的風險&#xff0c;需要對其進行建模與分析…

《解鎖LibTorch:開啟C++深度學習新征程》

《解鎖LibTorch:開啟C++深度學習新征程》 深度學習與 LibTorch 在當今數字化時代,深度學習已成為人工智能領域的核心驅動力,廣泛應用于計算機視覺、自然語言處理、語音識別等諸多領域,深刻改變著我們的生活和工作方式。它的發展歷程充滿了創新與突破,從最初的理論探索到如…

理想藥用植物的特征綜述-理想中藥材”的系統定義-文獻精讀125

Decoding and designing: Promising routes to tailor-made herbs 解碼與設計&#xff1a;定制化草藥的潛力路徑 摘要 理想藥用植物的特征可歸納為高次生代謝產物含量、高抗逆性、理想的形態以及高產量。本研究提出了兩種策略&#xff0c;用于解析中藥活性成分的生物合成與質…