vue3源码解析——ref和reactive定义响应式的区别

refreactive 是 Vue 3.0 中用于定义响应式数据的两个新 API。它们有以下区别:

ref 定义单个响应式数据

  • 数据类型可以是任意类型。它通常用于定义原始数据类型为响应式数据。
  • 返回一个响应式对象,该对象包含一个 .value 属性,可用于获取和设置数据。
  • 底层采用Object.defineProperty()实现

reactive 定义多个响应式数据

  • 数据类型必须是对象
  • 返回一个响应式对象,必须使用响应式对象来获取属性和设置数据
  • 底层采用的是new Proxy()

reactive源码解析

reactive 函数可以用来创建一个响应式的对象,它会在内部使用 Proxy 对对象的 getter 和 setter 进行拦截,从而实现对数据的依赖收集和通知更新。

在vue3源码里,reactive的实现在core-main\packages\reactivity\src\reactive.ts文件中

 reactive是一个函数,接收传入的target对象。返回一个createReactiveObject方法。

 reactive的核心逻辑在createReactiveObject方法中。

  • 首先校验传入的target必须是对象;
  • 通过new Proxy创建一个代理对象,根据对象的类型走不同的handler处理逻辑;
  • 返回代理对象proxy

proxy是怎么代理的?

手写一个简易的reactive,看下handler里是什么

  let handler = {//拦截整个对象,访问对象的属性时get拦截器触发get(target, key) {let value = target[key];if (typeof value === "object") {//如果访问的对象属性还是对象,进行递归return new Proxy(value, handler);}return value;},//拦截整个对象,当修改对象的属性的时候set拦截器会触发set(target, key, value) {target[key] = value;},};function reactive(target) {return new Proxy(target, handler);}let obj = { name: "jw", age: 30, n: [1, 2, 3, 4, 5] };//拿到obj的代理对象proxyObjconst proxyObj = reactive(obj);//不访问obj,访问代理对象proxyObjconsole.log(proxyObj.name); //触发get拦截器proxyObj.age = 31; //触发set拦截器proxyObj.name = 100; //设置一个不存在的属性

handler 对象中,可以定义多个方法来控制代理对象的行为。在响应式reactive中,handler主要用了get和set两个方法。(是不是很熟悉?Object.defineProperty也有get和set方法,只不过是针对属性的;proxy这个是针对对象的,所以不一样哦

  • get(target, property, receiver):拦截对象属性的读取操作,当访问代理对象的属性时会触发该方法。
  • set(target, property, value, receiver):拦截对象属性的设置操作,当给代理对象的属性赋值时会触发该方法。

 这样每次你通过访问代理对象proxyObj访问的属性,都会被handler的get方法拦截,进而收集依赖。对proxyObj修改属性值的时候,被handler的set方法拦截,进行依赖更新。

注意:你不是操作原始对象obj,而是操作的代理对象proxyObj

ref源码原理

在解析源码之前,先思考一个问题

为什么ref定义后的对象可以通过.value 访问和设置数据?

你有没有想过,为什么使用ref定义的对象或基本类型,在js里写的时候都需要用.value啊。

这里不得不提到一个特殊的名称——typeScript类的属性访问器:get和set属性。

get 和 set 属性是一种在类中定义属性的方式,可以用来实现类的属性的读取和写入操作。基本语法如下:

class MyClass {private _propertyName: any;get propertyName(): any {console.log("读取数据,自动进入get方法");return this._propertyName;}set propertyName(value: any) {this._propertyName = value;console.log("更新数据,自动进入set方法");}
}

在上面的例子中,propertyName 是一个类的属性,它使用 get 和 set 属性定义在 MyClass 类中。在类的外部,可以使用点操作符来读取和写入 propertyName 属性,例如:

const myObject = new MyClass();
myObject.propertyName = 'hello';//更新
console.log(myObject.propertyName); //访问

执行结果

当在类的外部读取 propertyName 属性时,会调用 propertyName 的 get 属性,返回 _propertyName 的值。当在类的外部写入 propertyName 属性时,会调用 propertyName 的 set 属性,将值赋给 _propertyName 属性。

为什么在类里这样写可以触发get和set呢?

这个是ts提供了一种写法,当我们在控制台使用tsc进行编译成.js文件后。

得到如下代码

var MyClass = /** @class */ (function () {function MyClass() {}Object.defineProperty(MyClass.prototype, "propertyName", {get: function () {console.log("读取数据,自动进入get方法");return this._propertyName;},set: function (value) {this._propertyName = value;console.log("更新数据,自动进入set方法");},enumerable: false,configurable: true});return MyClass;
}());
var myObject = new MyClass();
myObject.propertyName = "hello";
console.log(myObject.propertyName);

可以看到,最终propertyName编译成一个属性,并且通过Object.defineProperty进行了get和set方法的重写。 

OK,先说到这,带着这个理解去看ref的源码

vue3中ref实现

 在vue3源码里,ref的实现也是在reactivity文件夹下:core-main\packages\reactivity\src\ref.ts

 ref是一个函数,接收一个unknown类型的参数value;

返回一个createRef方法,第一个参数是value,第二个参数是false.

 createRef最终返回一个RefImpl实例对象。在RefImp类里,首先通过构造函数,创建一个_value对象。toReactive根据value类型,如果是对象的话,用reactive方法处理对象。

RefImpl类还定义了get和set方法,在类里使用"get空格value"这种方式很少见。这其实是ts类的属性访问器写法,当使用 .value 属性来获取或设置数据时,会自动调用 getset 访问器函数。

在底层,这种写法会被编译成 Object.defineProperty,这是 JavaScript 中用于定义对象属性的原生方法。使用 Object.defineProperty 可以定义对象的属性的特性,如可枚举性、可配置性、可写性等,并可以为属性设置 getset 函数,从而实现属性的访问器功能。(前面已经介绍过原理了

toReactive方法做了什么?

toReactive就是判断ref传入的是不是对象,如果是对象,去调用reactive方法将对象变成响应式的。ref这个老六,就是万精油,啥都能处理。

 思考:为什么你更习惯使用ref而不是reactive?

在Vue3中,无论是基本类型还是复杂类型的响应式数据,都推荐使用ref来创建。这样做的好处是,你可以统一使用.value属性来访问和修改响应式数据的值,而不需要在基本类型和复杂类型之间切换使用方式。

  1. 简单性ref 提供了一种简单的方式来定义响应式对象,只需传入初始值即可。相比之下,reactive 需要将整个对象传入,稍显繁琐。对于单个变量或简单数据,使用 ref 更加直观和方便。

  2. 透明性ref 返回的是一个包装过的对象,可以通过 .value 访问其值,这种包装使得数据访问更加明确和直观。而 reactive 返回的是原始对象,需要通过代理访问属性,有时会增加代码的复杂性。(复杂的代码谁想写啊)

  3. 性能:在某些情况下,ref 比 reactive 更高效。因为 ref 包装的是基本类型数据,而 reactive 包装的是对象,对于简单数据类型,ref 的性能可能更好。

  4. 推荐度:Vue 3 官方文档和社区更倾向于推荐使用 ref,因为它更简单、更直观,适用于大多数场景。而 reactive 更适合处理复杂的对象或数据结构。

尽管程序员通常建议使用 ref,但在实际开发中,根据具体情况选择合适的方式是更为重要的。对于简单的数据,使用 ref 可能更加方便和直观;而对于复杂的对象或数据结构,使用 reactive 可能更合适。

 看到这的,给我来波666,太烧脑了

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

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

相关文章

java学习之路-数组定义与使用

目录 ​编辑 1.什么是数组 2.数组的创建及其初始化 2.1数组的创建 2.2数组的初始化 3.数组的使用 3.1数组元素访问 3.2遍历数组 4.数组是引用类型 4.1jvm的内存分布 4.2基本类型变量与引用类型变量的区别 4.3引用变量详解 4.4 null 5.数组的使用场景 5.1存储数据 5…

在jupyter notebook中使用conda环境

在jupyter notebook中使用conda环境 1. 环境配置 conda activate my-conda-env # this is the environment for your project and code conda install ipykernel conda deactivateconda activate base # could be also some other environment conda install nb_cond…

harbor api v2.0

harbor api v2.0 v2.0 v2.0 “harbor api v2.0”与v1区别较大,此处harbor也做了https。另外,通过接口拿到的数据也是只能默认1页10个,所以脚本根据实际情况一页页的循环抓取数据 脚本主要用于统计repo(仓库)、image,以及所有镜像…

什么是智慧公厕?智慧城市下的智慧公厕有什么功能和特点?

随着科技的不断进步和城市化的加快发展,智慧城市已经成为我们生活中的一部分。而在智慧城市的建设中,智慧公厕作为城市基础设施的重要组成部分发挥着重要的作用。那么什么是智慧公厕?智慧公厕是针对公共厕所的日常使用、运行、管理、运营等过…

Python文件操作命令

文件操作 我知道你最近很累,是那种看不见的、身体上和精神上的疲惫感,但是请你一定要坚持下去。就算无人问津也好,技不如人也好,千万别让烦躁和焦虑毁了你的热情和定力。别贪心,我们不可能什么都有,也别灰心…

4月2日 qt密码生成小程序(可选择生成密码的格式),基于Python框架下的pyqt6

4月2日 密码生成小程序 代码展示: import stringfrom PyQt6.QtWidgets import (QApplication, QDialog,QMessageBox ) from untitled import Ui_PasswordGender import sys import string # py模块含有字符 import randomclass MyPasswordGenerate(Ui_Password…

【Vue3源码学习】— CH2.6 effect.ts:详解

effect.ts:详解 1. 理解activeEffect1.1 定义1.2 通过一个例子来说明这个过程a. 副作用函数的初始化b. 执行副作用函数前c. 访问state.countd. get拦截器中的track调用e. 修改state.count时的set拦截器f. trigger函数中的依赖重新执行 1.3 实战应用1.4 activeEffect…

Mysql数据备份与恢复实战

文章目录 备份类型备份内容备份工具mysqldump备份 实战案例:恢复误删除的表准备工作2:30完全备份完全备份后更新数据表10:00误删students表需要恢复还原的状态开始还原恢复 为什么要备份? 备份是为了:灾难恢复:硬件故障、软件故障…

Php_Code_challenge16

题目: 答案: 解析: 所以科学计数法绕过即可。

旅游管理系统|基于Springboot的旅游管理系统设计与实现(源码+数据库+文档)

旅游管理系统目录 目录 基于Springboot的旅游管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户管理 2、景点分类管理 3、景点信息管理 4、酒店信息管理 5、景点信息 6、游记分享管理 四、数据库设计 1、实体ER图 2、具体的表设计如下所示&#xf…

ZKFair 创新之旅,新阶段如何塑造财富前景

在当前区块链技术的发展中,Layer 2(L2)解决方案已成为提高区块链扩容性、降低交易成本和提升交易速度的关键技术,但它仍面临一些关键问题和挑战,例如用户体验的改进、跨链互操作性、安全性以及去中心化程度。在这些背景…

Python控制安卓模拟器——uiautomator2模块

Python控制安卓模拟器——uiautomator2模块 目录 Python控制安卓模拟器——uiautomator2模块介绍【1】安装 python【2】安装 adb1]下载[adb:[2]配置环境变量 【3】安装uiautomator2【4】连接设备(安卓模拟器)【5】u2指令控制设备常用指令 【6】安装weditor【7】元素操作元素属性…

小白的第一次sql注入实战

前言 当时最初接触安全的时候拿下的第一个shell,还是比较兴奋的,忽略一下文章写的很水。 有id尝试sql注入 找这种sql注入的站用sql检索就行了,但是最好挂代理用谷歌搜索,百度的话搜sql注入的很多被别人打过了,导致链…

钉钉服务端API报错 43008 参数需要multipart类型

钉钉服务端API报错 43008 参数需要multipart类型 problem 使用媒体文件上传接口,按照文档输入参数,结果返回报错 # 参数 {"access_token": "xxx""type": "image","media": "/Users/xxx/xxx/s…

hcia datacom课程学习(5):MAC地址与arp协议

1.MAC地址 1.1 含义与作用 (1)含义: mac地址也称物理地址,是网卡设备在数据链路层的地址,全世界每一块网卡的mac地址都是唯一的,出厂时烧录在网卡上不可更改 (2)作用&#xff1a…

constexpr与std::is_same_v碰撞会产生什么火花?

1. 只编译会用到的if分支 示例代码一中,checkType_v1和checkType_v2两个函数的区别就是if的条件里一个加了constexpr一个没加,加与不加从结果来看都一样,那在编译时和运行时各有什么区别呢? 示例代码一,test_01.cpp&…

Linux之实现Apache服务器监控、数据库定时备份及通过使用Shell脚本发送邮件

目录 一、Apache服务器监控 为什么要用到服务监控? 实现Apache服务器监控 二、数据库备份 为什么要用到数据库备份? 实现数据库备份 三、Shell脚本发送邮件 为什么要用使用Shell脚本发送邮件? 实现Shell脚本发送邮件 一、Apache服务器…

大模型日报20240401

大模型实时打《街霸》捉对PK,GPT-4居然不敌3.5,新型Benchmark火了 链接:https://news.miracleplus.com/share_link/22340 让大模型直接操纵格斗游戏《街霸》里的角色,捉对PK,谁更能打?GitHub上一种你没有见…

Windows下配置及使用Git+rsync构建文件同步工具

背景(了解): 公司的前端项目里有bash脚本,开发人员在开发完,可以跑bash脚本自动发布部署到测试环境。但是windows是没办法直接跑bash脚本的,最便捷的做法就是借用git bash这个终端来跑项目里的bash脚本。但是windows的源里并没有…

conda使用记录

linux 使用conda创建新一个新的python环境过程 conda create -n recommendation_env python3.8.18 # 指定python版本 conda env list # 查看所有的环境 conda activate recommendation_env # 激活创建的新环境 pip install flask # 安装依赖 或者 pip install flask版本号 或者…