vue3 reactive原理(二)-代理Set和Map及ref原理

 Set和Map类型的数据也属于异质对象,它们有特定的属性和方法用来操作自身。因此创建代理时,针对特殊的方法需要特殊的对待。

Vue 的ref 是基于reactive函数实现的,它在其基础上,增加了基本类型的响应性、解决reactive在解构时丢失响应性的问题及在模版字面量中自动脱落Ref.

1 代理Set和Map

function createReactive(obj,isShallow = false) {return new Proxy(obj, {get(target, p, receiver) {track(target,p)const value = Reflect.get(target,p,receiver)if (isShallow) return valuereturn typeof value === 'object' && value !== null ? reactive(value) : value},// 省略其他代码})
}const proxyObj = reactive(new Set())
// 报错 Method get Set.prototype.size called on incompatible receiver 
console.log(proxyObj.size)

上面代码报错:receiver上不兼容size方法。这是因为上面代码中,receiver 指向Proxy的代理对象,它是没有size方法的。下面是对get方法改进。

get(target, p, receiver) {if (p === 'size') return Reflect.get(target,p,target)// 省略其他代码
},

但是,改进后再执行proxyObj.add(1),又报错:receiver 上不兼容add方法。因为是proxyObj执行add函数,add函数里的this始终指向proxyObj。这是可以使用函数的bind方法,来绑定函数中this的值。

get(target, p, receiver) {// 省略其他代码const value = Reflect.get(target,p,receiver)if (typeof value === 'function') return value.bind(target)if (isShallow) return valuereturn typeof value === 'object' && value !== null ? reactive(value) : value
},

1.1 建立响应联系

  1. size属性是一个只读属性,Set的add、delete会改变它的值。
  2. 调用Set的add方法时,如果元素已存在于Set中,就不需要触发响应。调用Set的delete方法时,如果元素不存在于Set中,也不需要触发响应。
const mutableInstrumentation = {add(key) {const target = this.rawconst hadKey = target.has(key)const res = target.add(key)if (!hadKey && res) {trigger(target, key, 'ADD')}return res},delete(key) {const target = this.rawconst hadKey = target.has(key)const res = target.delete(key)if (hadKey && res) {trigger(target, key,'DELETE')}return res}
}
// Proxy 中的get代理
get(target, p, receiver) {// 省略其他代码track(target,p)if (mutableInstrumentation.hasOwnProperty(p)) {return mutableInstrumentation[p]}// 省略其他代码
},

1.2 避免污染原始数据

const proxySet = reactive(new Set())
const map = new Map()
const proxyMap = reactive(map)
proxyMap.set('set',proxySet)
effect(() => {console.log(map.get('set').size)
})
console.log("----------------")
proxySet.add(1) // 触发响应

上面原始数据具有了响应性,这不符合需求(原始数据应不具备响应性)。产生这个的原因是,在设置值时,直接把响应体对象也添加进原始对象了。所以,解决的关键在于:设置值时,如果该对象是响应体对象,则取其目标对象。

// mutableInstrumentation 对象的set方法
set(key,value) {const target = this.rawconst had = target.has(key)const oldValue = target.get(key)target.set(key,value.raw || value) // 去目标对象if (!had) {track(target, 'key', 'ADD')} else if (oldValue !== value && (oldValue === oldValue || value === value)) {trigger(target,key,'SET')}
}

1.3 处理forEach

1)forEach 只与键值对的数量有关,所以当forEach被调用时,让ITERATE_KEY与副作用函数建立联系。

2)当set方法设置的属性存在时,但属性值不同时,也应该触发forEach。

3)forEach函数的参数callback,它是有原始对象调用的,这意味着callback函数中的value及key两个参数不具有响应性,但是它们应该都具备响应性,需要将这两个参数转成响应体。

// mutableInstrumentation 对象的forEach方法
forEach(callback) {const target = this.rawconst wrap = (val) => typeof val === 'object' ? reactive(val) : valtrack(target,ITERATE_KEY)target.forEach((v,k) => {callback(wrap(v),wrap(k),this)})
}function trigger(target,p,type,newValue) {const map = effectMap.get(target)if (map) {// 省略其他代码if (type === 'ADD' || type === 'DELETE' || (type === 'SET' && Object.prototype.toString.call(target) === '[object Map]')) {// 省略其他代码}// 省略其他代码		}
}

1.4 迭代器方法

集合类型有三个迭代器方法:entries、keys、values,还可以使用 for...of进行迭代。

  1. 使用for...of迭代一个代理对象时,内部会调用[Symbol.iterator]()方法,返回一个迭代器。迭代产生的值不具备响应性,所以需要把这些值包装成响应体。
  2. 可迭代协议指一个对象实现了Symbol.iterator方法,迭代器协议是指一个对象实现了next方法。而entries方法要求返回值是一个可迭代对象,即该对象要实现了Symbol.iterator方法。
  3. values 方法,返回的仅是Map的值,而非键值对。
  4. keys 方法,与上面不同的是,调用set时,如果非添加值,则不应该触发响应。
function trigger(target,p,type,newValue) {const map = effectMap.get(target)if (map) {// 省略其他代码if (type === 'ADD' || type === 'DELETE' && Object.prototype.toString.call(target) === '[object Map]') {const tempSet = map.get(MAP_KEY_ITERATE_KEY)tempSet && tempSet.forEach(fn => {if (activeEffectFun !== fn) addSet.add(fn)})}addSet.forEach(fn => fn())}
}const mutableInstrumentation = {// 省略其他代码[Symbol.iterator]: iterationMethod,entries: iterationMethod,values: valueIterationMethod,keys: keyIterationMethod
}function iterationMethod() {const target = this.rawconst itr = target[Symbol.iterator]()const wrap = (val) => typeof val === 'object' && val != null ? reactive(val) : valtrack(target,ITERATE_KEY)return {next() {const {value,done} = itr.next()return {value: value ? [wrap(value[0]),wrap(value[1])] : value,done}},[Symbol.iterator]() {return this}}
}
function valueIterationMethod() {const target = this.rawconst itr = target.values()const wrap = (val) => typeof val === 'object' && val != null ? reactive(val) : valtrack(target,ITERATE_KEY)return {next() {const {value,done} = itr.next()return {value: wrap(value),done}},[Symbol.iterator]() {return this}}
}
function keyIterationMethod() {const target = this.rawconst itr = target.keys()const wrap = (val) => typeof val === 'object' && val != null ? reactive(val) : valtrack(target,MAP_KEY_ITERATE_KEY)return {next() {const {value,done} = itr.next()return {value: wrap(value),done}},[Symbol.iterator]() {return this}}
}

2 原始值的响应方案ref

Proxy 的代理目标必须是非原始值,如果要让原始值具有响应性,那么要对它进行包装。Vue3 的ref函数就负责这个工作。

function ref(val) {const wrapper = {value: val}// 为了区分数据是经过ref包装的,还是普通对象Object.defineProperty(wrapper,'_v_isRef',{value: true})return reactive(wrapper)
}

2.1 reactive 解构时丢失响应性

const proxyObj = reactive({name: 'hmf',num: 1})
const obj = {...proxyObj} // obj 不再具有响应性。这是因为解构时
{…proxyObj} 等价于 {name: 'hmf',num: 1}

要让obj 具有响应性,则需要使其属性值为一个对象。如下所示:

const obj = {name: {get value() {return proxyObj.name},set value(val) {proxyObj['name'] = val}},num: {get value() {return proxyObj.num},set value(val) {proxyObj['num'] = val}}
}

ref函数优化如下:

function toRefs(obj) {const ret = {}for (const key in obj) {ret[key] = toRef(obj,key)}return ret
}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
}

2.2 自动脱ref

经过toRefs处理的对象,都需要通过对象的value属性来访问,例如

const proxyObj = ref({name: 'hmf',num: 1})
console.log(proxyObj.name.value)
proxyObj.name.value = 'hi'

访问任何属性都需要通过value属性访问,这增加了用户的心智负担。我们需要自动脱ref的能力,即上面proxy.name就可直接访问。

function proxyRefs(target) {return new Proxy(target, {get(target, p, receiver) {const value = Reflect.get(target,p,receiver)return value._v_isRef ? value.value : value},set(target, p, newValue, receiver) {const value = target[p]if (value._v_isRef) {value.value = newValuereturn true}return Reflect.set(target,p,newValue,receiver)},})
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/49774.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

目标检测原理分析

目标检测 图像分类(一张图像只属于一个类别)目标检测(一张图像有N个物体) 图像分类(一张图像只属于一个类别) 输入: 一张图像,image 输出: 这张图像属于各个预定义类别的…

【Unity实战】yield return null还是WaitForEndOfFrame

当在Unity中编写协程(尤其是协程套无限循环)时,常常会用到yield关键字来控制协程的执行流程避免程序假死。以下是常见做法: yield return null 当使用yield return null时,协程会在下一帧继续执行。这意味着协程将暂…

SSM超市管理系统-计算机毕业设计源码12393

目 录 摘要 Abstract 1 绪论 1.1研究的背景和意义 1.2研究内容 1.3论文结构与章节安排 2 开发技术介绍 2.1 SSM框架 2.2 MySQL数据库 3 超市管理系统系统分析 3.1 可行性分析 3.2 系统流程分析 3.2.1 数据流程 3.3.2 业务流程 3.3 系统功能分析 3.3.1 功能性…

机器学习 | 回归算法原理——最速下降法(梯度下降法)

Hi,大家好,我是半亩花海。接着上次的最小二乘法继续更新《白话机器学习的数学》这本书的学习笔记,在此分享最速下降法(梯度下降法)这一回归算法原理。本章的回归算法原理基于《基于广告费预测点击量》项目,…

使用 AntV G2 绘制折线图

本文由ScriptEcho平台提供技术支持 项目地址:传送门 使用 AntV G2 绘制折线图 应用场景介绍 AntV G2 是一款基于 Vue 的数据可视化框架,可用于创建交互式图表。本代码展示了如何使用 AntV G2 绘制折线图,以可视化时间序列数据。 代码基本…

数据结构——队列(java实现)及相应的oj题

文章目录 前言队列队列的概念队列的实现队列的链表实现实现的方法与属性内部类实现节点入队列出队列获取队头元素但不删除判空获取队列元素个数 队列的数组实现循环队列方法属性实现:构造方法向循环队列插入一个元素,成功插入则为真。从循环队列中删除一…

Axivion Suite 7.8现已发布

现已实现100%覆盖MISRA规则,并加入了高级功能来提高代码分析能力。 我们很高兴地宣布Axivion Suite 7.8发布。全新版本的Axivion Suite对编译器、配置、分析、仪表板 (WebUI)和IDE插件的架构验证和静态代码分析功能均进行了升级。 100%覆盖所有可测试的MISRA规则 …

昇思25天学习打卡营第15天|K近邻算法实现红酒聚类

这个实验是关于如何使用MindSpore框架在红酒数据集上实现K近邻(KNN)算法来进行聚类分析的。KNN是一种简单但非常有效的机器学习算法,它通过计算样本之间的距离来决定其分类KNN算法的核心思想是,一个样本的类别可以通过它与训练集中…

Pytorch使用教学1-Tensor的创建

0 导读 在我们不知道什么是深度学习计算框架时,我们可以把PyTorch看做是Python的第三方库,在PyTorch中定义了适用于深度学习的张量Tensor,以及张量的各类计算。就相当于NumPy中定义的Array和对应的科学计算方法,正是这些基本数据…

【Nacos安装】

这里写目录标题 Nacos安装jar包启动Docker单体Docker集群 Nacos相关配置日志配置 Nacos安装 jar包启动 下载jar包 在官方下载链接,根据需求选择相应的版本下载。 解压 tar -zxvf nacos-server-2.4.0.1.tar.gz或者解压到指定目录 tar -zxvf nacos-server-2.4.0…

HarmonyOS Next原生应用开发-从TS到ArkTS的适配规则(九)

一、需要显式标注泛型函数类型实参 规则:arkts-no-inferred-generic-params 级别:错误 如果可以从传递给泛型函数的参数中推断出具体类型,ArkTS允许省略泛型类型实参。否则,省略泛型类型实参会发生编译时错误。 禁止仅基于泛型函数…

Perl脚本学习(一)-- 基础语法

系列文章目录 第一章 Perl脚本学习(一)-- 基础语法 一、Perl Perl 程序由声明与语句组成,程序自上而下执行,包含了循环,条件控制,每个语句以分号 ; 结束。Perl 语言没有严格的格式规范。 二、Perl简单使用…

TikTok达人合作中的消费者行为研究:精准营销新趋势

随着全球社交媒体技术的飞速发展,TikTok作为短视频领域的佼佼者,其独特的达人带货模式不仅成为驱动消费市场发展的新力量,还深刻改变了消费者的购买行为。本文Nox聚星将和大家探讨TikTok达人合作过程中消费者的行为模式和偏好变化。 一、消费…

SkyWalking入门搭建【apache-skywalking-apm-10.0.0】

Java学习文档 视频讲解 文章目录 一、准备二、服务启动2-1、Nacos启动2-2、SkyWalking服务端启动2-3、SkyWalking控制台启动2-4、自定义服务接入 SkyWalking 三、常用监控3-1、服务请求通过率3-2、服务请求拓扑图3-3、链路 四、日志配置五、性能剖析六、数据持久化6-1、MySQL持…

认识R与数据库连接和网络爬虫,学会在R中使用SQL语言

R语言作为一种强大的统计计算和数据分析工具,不仅在数据处理和可视化方面表现出色,还在与数据库连接和网络爬虫方面具备强大功能。本文将介绍如何在R中进行数据库连接和网络爬虫,并展示如何使用SQL语言在R中进行数据操作。 一、R与SQL数据库 1、认识SQL数据库 SQL(Struc…

企业怎么才能用上大语言模型?

题图|视觉中国 以ChatGPT为起点,大语言模型(LLM)用全面的技术创新,以及在用户和产业中的应用落地,再次掀起了一个AI新浪潮。 与它的前辈们相比,大语言模型因为打通了语言这一人类沟通中介&…

【并发编程】-4.Lock接口与AQS

Lock接口概念 概念:JDK1.5时,在Java.uitl.concurrent并发包中添加了Lock锁接口,其中定义了lock()、unLock()、tryLock()、lockInterruptibly()、newCondition()等一系列接口,它属于是JAVA语言层面的内置锁;特性&#…

47、PHP实现机器人的运动范围

题目: PHP 实现机器人的运动范围 描述: 地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k…

探索云计算的未来:边缘计算如何重塑IT格局

目录 一、引言 二、边缘计算的基本概念与特性 1. 基本概念 2. 技术特性 三、边缘计算如何重塑IT基础设施 1. 分布式计算架构的兴起 2. 边缘节点的部署与管理 3. 数据流与业务逻辑的重新设计 4. 安全性与隐私保护的加强 四、边缘计算在关键行业的应用实践 1. 智能制造…

关联查询(xml)

多对多:数据库中需要有中间表,在两个实体类中都加入对方的List集合,在写查询语句时写三张表