《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.mzph.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;用于解析中药活性成分的生物合成与质…