11_原始值的响应式方案-ref

目录

    • 引入 ref
    • 解决响应丢失的问题
    • 自动脱 ref

引入 ref

在之前实现的 reactive 方法,其代理的目标必须是一个非原始值才行,例如:

let str = 'vue'
// 无法拦截 str 的修改
str = 'vue3'

上述这个例子表达的意思就是,我们还缺少一个能够对原始值实现响应式数据的手段。

对于这个问题,基于现有的 reactive 就一定毫无办法吗,其实也不尽然,也有一些曲线救国的手段,如下:

const wrapper = {value: 'vue'
}// 使用 proxy 代理 wrapper,间接实现对原始值的拦截
const name = reactive(wrapper)
name.value // vue
// 修改值-触发响应拦截
name.value = 'vue3'

这样确实是可以进行拦截,但是它存在两个问题:

  • 用户为了创建一个响应式对象的原始值,不得不顺带创建一个包裹对象,使用不方便
  • 包裹对象由用户定义,而这意味着不规范,用户可以随意命名,例如:wrapper.value、wrapper.v、wrapper.v,甚至如果用户愿意,可以写成语义错乱的情况,比如:wrapper.size。

而为了解决以上两个问题,我们需要封装一个函数,来进行规范,如下:

// 封装一个 ref 函数
function ref(val){// 在 ref 函数内创建包裹对象const wrapper = {value: val}// 将包裹对象变成响应式数据return reactive(wrapper)
}

这样就解决了上述的两个问题,我们来添加一段测试代码如下:

const refVal = ref(1)effect(() => {console.log('effect-ref:', refVal.value)
})refVal.value++

结果如图:

在这里插入图片描述

这段代码可以按照我们预期的工作,他还存在诸多的问题,第一个面临的就是如何区分这是一个 ref 对象,即如何区分 refVal 到底是原始值的包裹对象,还是一个非原始值的响应式数据,如如下代码所示:

const refVal1 = ref(1)
const refVal2 = reactive({value: 1})

如果放入 effect 函数中执行,这两个代码都是一样的效果。但是我们有必要区分一个数据到是是不是 ref,因为这对于后续的自动脱 ref 能力至关重要。

而想区分也并不难,在 reactive 中我们是在 get 拦截器中通过一个内置的 Symbol 为 key 来实现的,这里我们可以使用类似的方式,不过由于没有使用代理,我们可以使用 Object.defineProperty,如下:

function ref(val) {const wrapper = {value: val}// 添加一个特殊属性,用于标识这个对象是否是一个 ref 对象//  - 且这个属性是不可枚举的,防止被遍历到Object.defineProperty(wrapper, '__v_isRef', {value: true})return reactive(wrapper)
}

通过这个属性就可以实现是否是一个 ref 对象

解决响应丢失的问题

ref 除了能够用于原始值的响应式方案之外,还可以用来解决响应式丢失的问题,首先,我们来看一下什么叫响应式丢失,在编写 vue.js 组件时,我们通常把数据暴露到模板中使用,例如:

export default {setup(){// 响应式数据const obj = reactive({ foo: 1, bar: 2 })// 将数据暴露到模板中return {...obj}}
}

然后在模板中可以访问从 setup 中暴露的数据,如下:

<template><p> {{ foo }} / {{ bar }} </p>  
</template>

然而,这样书写就会失去响应式,当我们修改数据时,不会触发渲染,这是为什么?因为我们使用了展开运算符,而经过展开之后,等价于:

return {foo: 1,bar: 2
}

而这,就是一个普通对象,自然不具备响应式能力,我们也可以通过我们熟悉的 effect 案例来描述这个例子,如下:

const obj = reactive({ foo: 1, bar: 2 })// 将响应式数据战展开到一个新的对象 newObj
const newObj = {...obj
}effect(()=>{// 在副作用函数内使用新的对象 newObj 读取 foo 属性值console.log(newObj.foo)
})// 修改-无法触发响应
obj.foo = 100

如何解决这个问题呢?或者说:在副作用函数中,即时通过普通对象 newObj 来访问属性值,也能够建立响应联系,如下:

const obj = reactive({ foo: 1, bar: 2 })// newObj 具备 obj 的同名属性,并且每个属性值都是一个对象,这个对象具备一个访问器属性 value,当读取 value 属性,其实读取的就是 obj 下对应的属性值
const newObj = {foo: {get value(){return obj.foo}},bar: {get value(){return obj.bar}}
}effect(()=>{console.log(newObj.foo)
})// 可以触发响应
obj.foo = 100

这样就可以实现响应的触发了,仔细观察就不难发现 foo 和 bar 的处理都是一致的,对于这种我们可以通过封装一个函数来提高效率,如下:

function toRef(obj, key) {const wrapper = {get value() {return obj[key]}}return wrapper
}

有了这个函数之后,我们的 newObj 就可以大大简化了,如下:

const newObj = {foo: toRef(obj, 'foo'),bar: toRef(obj, 'bar')
}

但是如果这样 foo 的属性非常多,处理起来也显得麻烦,我们可以在编写一个 toRefs 函数来完成,如下:

function toRefs(obj) {const ret = {}// 遍历对象中的每一个属性for (const key in obj) {// 调用 toRef 函数完成转换ret[key] = toRef(obj, key)}return ret
}

现在,我们的使用会更加的简洁,如下:

const newObj = { ...toRefs(obj) }

而由上述可得 toRef 会将一个数据转为 ref 数据,所以还需要为 toRef 函数补充一点,如下:

function toRef(obj, key) {const wrapper = {get value() {return obj[key]}}// 添加一个特殊属性,用于标识这个对象是否是一个 ref 对象//  - 且这个属性是不可枚举的,防止被遍历到Object.defineProperty(wrapper, '__v_isRef', {value: true})return wrapper
}

此时的 toRef 还只是只读的,因此我们还需要为其加上 setter,如下:

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
}

自动脱 ref

toRefs 解决了响应式丢失问题,也带了新的问题,由于 toRefs 会把第一层属性值转为 ref,因此必须通过 value 属性来访问,这样就会导致用户的心智负担,如下:

<!-- good -->
<div>{{ foo }} / {{ bar }}</div><!-- bad -->
<div>{{ foo.value }} / {{ bar.value }}</div>

因此,我们需要脱 ref 能力,所谓自动脱 ref,就是指的属性访问行为,即 ref 数据无需通过 xx.value 来访问。

而实现这个功能其实也不难,如下:

function proxyRefs(target) {return new Proxy(target, {get(target, key, receiver) {const value = Reflect.get(target, key, receiver)// 自动脱 ref 实现:如果 value 是一个 ref 对象,则返回它的 value 属性return isRef(value) ? value.value : value}})
}

可以看到,我们利用 proxy 的 get 拦截器作为插入代码逻辑的地方,在这里我们通过判断一个值是否是 ref 数据,如果是在自动的在这里读取 refVal.value。

而实际上,在编写 vue.js 组件的时候,组件中的 setup 函数所返回的数据会传递给 proxyRefs 来处理:

const myComp = {setup(){const count = ref(0)// 返回的这个对象会交给 proxyRefs 来处理return { count }}
}

这样在模板中,我们就无需通过 xx.value 来访问属性值。而可以读取,自然也需要添加修改,如下:

function proxyRefs(target) {return new Proxy(target, {get(target, key, receiver) {const value = Reflect.get(target, key, receiver)return isRef(value) ? value.value : value},set(target, key, newValue, receiver) {const oldValue = target[key]// 如果是 ref,则设置其对应的 value 属性值if (isRef(oldValue)) {oldValue.value = newValuereturn true}return Reflect.set(target, key, newValue, receiver)}})
}

有了自动脱 ref 的能力之后,就可以降低用户在使用时的心智负担,无需关心那个属性是 ref 那个属性是普通数据或者是 reactive。

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

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

相关文章

ZYNQ:流水灯实验

实验目的 PL_LED0 和 PL_LED1 连接到 ZYNQ 的 PL 端&#xff0c;PL_LED0 和 PL_LED1循环往复产生流水灯的效果&#xff0c;流水间隔时间为 0.5s。 原理图 程序设计 本次实验是需要实现两个LED的循环熄灭点亮&#xff0c;时间间隔是0.5S,对时间间隔的控制使用计数器来完成。本…

第十九课:Python学习之继承

继承 目标 单继承多继承 面向对象三大特性 封装 根据 职责 将 属性 和 方法 封装 到一个抽象的 类 中继承 实现代码的重用&#xff0c;相同的代码不需要重复的编写多态 不同的对象调用相同的方法&#xff0c;产生不同的执行结果&#xff0c;增加代码的灵活度 01. 单继承 …

安装buildkit,并使用buildkit构建containerd镜像

背景 因为K8s抛弃Docker了,所以就只装了个containerd,这样就需要一个单独的镜像构建工具了,就用了buildkit,这也是Docker公司扶持的,他们公司的人出来搞的开源工具,官网在 https://github.com/moby/buildkit 简介 服务端为buildkitd,负责和runc或containerd后端连接干活,目前…

MySQL 查询按照更新时间排序遇到相同更新时间的会少数据

MySQL分页后出现重复数据或丢失记录的原因可能包括&#xff1a;SQL查询条件不一致、使用了不稳定的排序、LIMIT语句与ORDER BY配合问题、缓存设置不当或数据库复制配置错误。需要检查查询逻辑和系统配置以解决这些问题。 MySQL分页导致数据重复的原因&#xff1a; 1、排序算法…

如何有效维护您的WordPress在线商店内容:提高客户参与度与转化率的实用技巧

在电子商务领域&#xff0c;内容为王。新鲜、相关且有吸引力的内容能显著提升客户参与度和转化率。本文将探讨如何有效更新和维护您的在线商店内容&#xff0c;确保客户始终获得最佳体验。 定期更新产品信息 产品描述 产品描述是吸引客户和促成销售的关键。定期检查并更新产…

Netty基础原理

1.Netty概述 Netty是由JBoss提供的开源的高性能、异步事件驱动的网络应用框架&#xff0c;通过Netty可以很快的构建出通信效率极高的应用&#xff1b; 异步与同步是相对的&#xff0c;在请求或执行过程中&#xff0c;如果会阻塞等待就是同步&#xff0c;反之就是异步 1.1 Net…

vue3纯前端验证码示例

前言 验证码的用途&#xff1a;通过要求用户输入一串难以被机器自动识别的字符或图像&#xff0c;有效阻止恶意用户或脚本通过暴力破解方式尝试登录账户。验证码的分类&#xff1a;常见的验证码有短信、文本、图形等&#xff0c;安全度越高&#xff0c;依赖的插件或服务也越多…

ENSP环回路由的配置

环回路由配置如下&#xff0c;网段及其基础配置已写完。为了实现全网通&#xff0c;需要给路由器手写配置&#xff0c;使用 ip route—static目标网段下一跳。把所有情况都要考虑到&#xff0c;就会出现去往一个网段的最优路径和次优路径&#xff0c;近路和远路都能前往目标网段…

Spire.PDF for .NET【页面设置】演示:在 C# 中应用 PDF 页面转换

当您在导出为 PDF 格式的文档中翻页时&#xff0c;页面过渡会显示装饰效果&#xff0c;例如溶解或擦除。当您以 PDF 格式创建幻灯片时&#xff0c;页面过渡特别有用。Spire.PDF是一款专为开发人员设计的强大 .NET 组件&#xff0c;可让您将页面过渡应用于 PDF 文件。 这里介绍…

【新专栏】Excel数据分析与模拟决策

专栏入口&#xff1a;Excel数据分析与模拟决策 主要面向使用Excel的用户&#xff0c;讲解Excel的数据分析&#xff0c;模拟分决策内容&#xff0c;内容涵盖基于Excel的统计分析&#xff0c;数据生成&#xff0c;采样&#xff0c;假设检验&#xff0c;线性回归&#xff0c;线性规…

大语言模型(LLM)入门级选手初学教程

链接&#xff1a;https://llmbook-zh.github.io/ 前言&#xff1a; GPT发展&#xff1a;GPT-1 2018 -->GPT-2&GPT-3&#xff08;扩大预训练数据和模型参数规模&#xff09;–> GPT-3.5&#xff08;代码训练、人类对齐、工具使用等&#xff09;–> 2022.11 ChatG…

海外云手机:出海电商养号智能化方案

随着出海电商的迅猛发展&#xff0c;使用海外云手机进行养号已经成为越来越多商家的新选择。尤其在社交电商推广和短视频引流方面&#xff0c;海外云手机不仅提高了流量的精准度&#xff0c;还助力商家实现业务的快速增长。本文将探讨海外云手机养号相较于传统模式的优势&#…

STM32CubeIDE(Eclipse)Post-build steps添加带参.exe实现全流程(1):带参.exe制作

0 工具准备 vscode&#xff0c;编辑C代码 Code Runner插件&#xff0c;用于生成exe 1 前言 使用STM32CubeIDE编译生成了二进制镜像文件后&#xff0c;有时为了防止镜像被恶意修改&#xff0c;可以通过添加校验和来对整个镜像进行保护&#xff0c;实现手段就是在STM32CubeIDE工…

精华分享 | 大模型LLM微调技巧与实践总结

导读 本文总结了作者在ChatGLM-6B模型微调的经验&#xff0c;并汇总了目前开源项目&数据。 写在前面 大型语言模型横行&#xff0c;之前非常焦虑&#xff0c;现在全面拥抱。目前也有很多开源项目进行大模型微调等&#xff0c;笔者也做了一阵子大模型了&#xff0c;特此来…

datawhale大模型bot应用开发--task4:图片流

一、图像流是专门用于图像处理的一个流程工具 在图像流中&#xff0c;你可以通过可视化的操作方式灵活添加各种用于图像处理的节点&#xff0c;构建一个图像处理流程来最终生成一个图像。图像流发布后&#xff0c;支持在 Bot 或工作流中使用。 二、图像流的能力范围 类型 图…

【Linux】进程--详细解释进程

目录 1、冯诺依曼体系结构 2、操作系统 3、进程 4、环境变量 5、进程地址空间 6、页表&#xff08;简单初步理解&#xff09; 1、冯诺依曼体系结构 a、存储器指的是&#xff1a;内存 外设&#xff1a; b、输入设备&#xff1a;鼠标&#xff0c;键盘&#xff0c;摄像头&am…

宠物用品在线交易:SpringBoot开发实战

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

Python入门(一)

心血来潮就干&#xff1a;python课程走起 和你一起学习 Python 语言的基础语法。你将会系统性地学习 Python 基础知识&#xff1b; 直接开始吧&#xff5e; 本篇文章将了解编程的几个基础概念。并且&#xff0c;你将学到如何在电脑屏幕上输出数字、文字&#xff0c;并敲出你…

ChatGLM4重磅开源! 连忙实操测试一波,效果惊艳,真的好用!附带最新相关资料和喂饭级实操推理教程!!

本文目录 GLM4重磅开源啦 GLM4系列版本介绍 GLM4大模型能力测评结果 经典测评任务结果 长文本能力 工具调用能力 多模态能力 手把手实操GLM-4-9B-Chat推理预测&&效果展示 GLM4运行硬件和环境要求 配置对应的库环境 使用peftbitsandbytes 进行4位量化推理 进…

Java基础12-特殊文件和日志技术

十二、特殊文件和日志技术 1、特殊文件 properties&#xff1a;用来存储键值对数据。 xml&#xff1a;用来存储有关系的数据。 1.1 properties文件 特点&#xff1a;存储键值对&#xff0c;键不能重复&#xff0c;文件后缀一般是.properties结尾的。 properties&#xff1a;是…