vue3 源码解析(2)— ref、toRef、toRefs、shallowRef 响应式的实现

news2024/11/17 16:25:02

前言

vue3 源码解析(1)— reactive 响应式实现

介绍完 reactive 之后还有另一个很重要的响应式API,其中包括 reftoReftoRefsshallowRef。这些API在vue3中起着至关重要的作用,它们帮助我们更好地管理和跟踪响应式数据的变化。本文还是通过举例子的形式逐一介绍这些API的工作原理。

ref

举个例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ref</title>
</head>
<body>
<div id="app"></div>
<!--响应式模块的代码-->
<script src="../packages/reactivity/dist/reactivity.global.js"></script>
<script>
  let { ref, effect } = VueReactivity;
  let name = ref("ref");
  effect(() => {
    app.innerHTML = name.value
  });
  setTimeout(() => {
    name.value = "hello";
  }, 1000);
</script>
</body>
</html>

在这里插入图片描述
通过例子可以看到1s之后改变数据视图也跟随变,在 vue3 中是那如何实现这一效果的呢?我们先从例子中的 ref 函数出发。

实现响应式

ref 函数接收一个内部值,然后返回一个具有响应性和可变性的 ref 对象。ref 对象有一个.value属性,该属性指向内部值。换句话说,无论你传递给 ref 函数什么样的值,它都会返回一个新的对象,这个对象有一个 .value 属性,你可以通过这个属性来获取或设置原始值。

function ref (target) {
  return createRef(target)
}

function createRef (rawValue, shallow = false) {
  return new RefImpl(rawValue, shallow)
}

class RefImpl<T> {
  private readonly _v_isRef = true // 标识 ref
  private _value: T
  private _rawValue: T
  constructor (value: T, public readonly __v_isShallow: boolean) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    this._value = __v_isShallow ? value : toReactive(value)
  }

  // 类的属性访问器并进行依赖收集
  get value () {
    track(this, 'get', 'value')
    return this._value
  }

  set value (newVal) {
    const useDirectValue = this.__v_isShallow
    if (hasChanged(newVal, this._value)) {
      this._rawValue = newVal
      this._value = useDirectValue ? newVal : toReactive(newVal)
      trigger(this, 'set', 'value', newVal)}
  }
}

这里重点看下 RefImpl 这个类的作用是什么:

  1. RefImpl 是一个类,它用来实现 ref 函数的功能。 对象有一个 .value 属性,可以用来获取或设置原始值,并且会触发依赖收集和更新。

  2. RefImpl 的构造函数接收两个参数,value__v_isShallow。value 是要转换为 ref 对象的原始值,__v_isShallow 是一个布尔值,表示是否使用浅层响应式。RefImpl 会将 value 转换为 _rawValue_value 两个属性,_rawValue 是原始值的副本,_value 是原始值的响应式版本。如果 __v_isShallow 为 true,则 _rawValue_value 相同,不会进行深层响应式转换。

  3. RefImpl 还定义了一个属性访问器 value,用来实现 ref 对象的 .value 属性。当读取 value 时,会调用 track 函数进行依赖收集;当设置 value 时,会判断新值和旧值是否有变化,如果有变化,则更新 _rawValue_value,并调用 trigger 函数通知依赖更新。

  4. RefImpl 还有一个私有属性 _v_isRef,用来标识 ref 对象。这样可以在其他地方判断一个对象是否是 ref 对象,并进行相应的处理。

toRaw

const enum ReactiveFlags {
  RAW = '__v_raw'
}
interface Target {
  [ReactiveFlags.RAW]?: any
}

function toRaw<T>(observed: T): T {
  const raw = observed && (observed as Target)[ReactiveFlags.RAW]
  return raw ? toRaw(raw) : observed
}

toRaw 函数的作用是返回一个响应式对象的原始对象。这是一个可以用来临时读取而不会产生代理访问/跟踪开销,或者写入而不会触发更改的逃生舱。

toReactive

const toReactive = <T extends unknown>(value: T): T =>
  isObject(value) ? reactive(value) : value

toReactive 是一个函数,它接受一个值作为参数。如果这个值是一个对象,那么 toReactive 会使用 reactive 函数将这个对象转换为响应式对象。如果这个值不是对象,那么 toReactive 就直接返回这个值。这个函数的主要作用是将一个可能是对象的值转换为响应式对象。当我们设置 ref 对象的 .value 属性时,如果新值是一个对象,那么 toReactive 会确保这个新值是响应式的,从而使得我们可以追踪这个新值的变化。

执行过程

为了更好的理解每个函数是如何执行的,我们可以通过 debugger 来调试一下。

RefImpl 类的实例

在数据更新之前 RefImpl 类的实例对应的数据如下图所示。

在这里插入图片描述

targetMap

effect 函数执行完成之后,此时依赖收集完之后对应的 targetMap 数据如下图所示。

在这里插入图片描述

数据更新时

执行 name.value = "hello" 更新数据时会触发 set ,此时新值和旧值不一样会触发 trigger。触发 trigger 时会从 targetMap 的子项 depsMap 中获取对应的 effect 函数执行并直接返回最新的值。这里的执行过程与之前提到的 reactive 函数执行过程类似,不了解的可以参考之前的文章。

toRef

举个例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>toRef</title>
</head>
<body>
<div id="app"></div>
<script src="../packages/reactivity/dist/reactivity.global.js"></script>
<script>
  // toRef 将目标对象中的属性值变成 ref
  let { reactive, toRef, effect } = VueReactivity;
  let state = reactive({ name: "toRef" });
  let name = toRef(state, "name");
  effect(() => {
    app.innerHTML = name.value;
  });
  setTimeout(() => {
    name.value = "hello";
  }, 1000);
</script>
</body>
</html>

在这里插入图片描述
同样可以看到1s之后改变数据视图也跟随变。

实现响应式

toRef 函数可以将一个响应式对象的属性转换为一个 ref 对象。这个 ref 对象会与源对象的属性保持同步,也就是说,修改源对象的属性会更新 ref 对象,反之亦然。

function toRef (target, key: string) {
  return new ObjectRefImpl(target, key)
}

class ObjectRefImpl<T extends object, K extends keyof T> {
  private readonly _v_isRef = true // 标识 ref
  constructor (public readonly  _object: T, public readonly  _key: K) {}
  get value () {
    return this._object[this._key]
  }
  set value (newValue) {
    this._object[this._key] = newValue
  }
}

ObjectRefImpl 类有以下几个特点:

  1. 它有一个私有属性 _v_isRef ,用来标识 ref 对象。

  2. 它有两个公共属性 _object_key ,分别表示源对象和属性名。

  3. 它有一个属性访问器 value ,用来实现 ref 对象的 .value 属性。当读取 value 时,会返回源对象的对应属性值;当设置 value 时,会更新源对象的对应属性值。

执行过程

ObjectRefImpl 类的实例

在数据更新之前 ObjectRefImpl 类的实例对应的数据如下图所示。

在这里插入图片描述

targetMap

因为这里的数据已经被处理成了响应式,当访问 name.value 时实质上是触发 reactive 函数中 reactiveHandlersget 拦截器,所以这里不需要手动触发 track 。此时依赖收集完之后对应的 targetMap 数据如下图所示。

在这里插入图片描述

数据更新时

同理当访问 name.value = "hello" 时实质上是触发 reactive 函数中 reactiveHandlersset 拦截器。所以这里也不需要手动触发 trigger ,之后的更新过程和之前类似这里不在赘述。

toRefs

举个例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>toRefs</title>
</head>
<body>
<div id="app"></div>
<script src="../packages/reactivity/dist/reactivity.global.js"></script>
<script>
  let { reactive, toRefs, effect } = VueReactivity;
  let state = reactive({ name: "toRef" });
  let { name } = toRefs(state);
  effect(() => {
    app.innerHTML = name.value;
  });
  setTimeout(() => {
    name.value = "hello";
  }, 1000);
</script>
</body>
</html>

上述代码的实现效果和上面的一致,都是在1s之后数据改变视图也改变。

实现响应式

toRefs 函数用于将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是一个 ref 对象,与源对象的对应属性保持同步。这样做的好处是,你可以在不丢失响应性的情况下,将响应式对象的属性解构到各个变量中。

function toRefs (target) {
  let result = isArray(target) ? new Array(target.length): {}
  for (let key in target) {
    result[key] = toRef(target, key)
  }
  return result
}

toRefs 函数做了以下几件事:

  1. 首先,它创建了一个新的空对象或数组 result ,用于存放转换后的 ref 对象。

  2. 然后,遍历 target 的每个属性。对于每个属性,它调用 toRef(target, key) 来创建一个 ref 对象,并将这个 ref 对象存放到 result 的对应属性中。这样,result[key] 就成为了一个与 target[key] 保持同步的 ref 对象。

  3. 最后,它返回 result 。这样,你就可以像操作普通对象一样操作 result ,而不用担心丢失响应性。

具体的执行过程可以参考 toRef 这里就不在赘述。

shallowRef

举个例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>shallowRef</title>
</head>
<body>
<div id="app"></div>
<script src="../packages/reactivity/dist/reactivity.global.js"></script>
<script>
  let { shallowRef, effect } = VueReactivity;
  const state = shallowRef({ count: 1 })
  effect(() => {
    app.innerHTML = state.value.count
  });
  setTimeout(() => {
    state.value.count = 2
  }, 1000);
</script>
</body>
</html>

与之前不同的是1s之后数据改变了但是视图却并没有更新。

实现响应式

function shallowRef (target) {
  return createRef(target, true)
}

function createRef (rawValue, shallow = false) {
  return new RefImpl(rawValue, shallow) // 浅的
}

需要注意的是:

  1. 与之前创建 ref 函数不一样的是这个函数的第二个参数 true 表示创建的引用是浅的。这意味着,如果你更改了 target 的属性,vue 不会触发任何副作用或计算属性的重新计算。

  2. 如果你省略这个参数或将其设置为 false,那么 createRef 将创建一个深度引用,即 target 的所有属性都将被转换为响应式的。

  3. 如果你直接更改了 target(例如,将其设置为一个新的对象 state.value = { count: 2 }),vue 会触发响应。

执行过程

RefImpl 类的实例

在数据更新之前 RefImpl 类的实例对应的数据如下图所示。

在这里插入图片描述

数据更新时

需要注意的是执行 state.value.count = 2" 更新数据时触发的依然是 get 函数,直接返回原理的值。同时也不会触发 set函数和 effect 函数,所以这里的视图是不会进行更新的。

总结

这篇文章主要介绍了 vue 3 的响应式原理,其中涉及到了 reftoReftoRefsshallowRef 等函数的实现。下面是这些函数的响应式实现的总结:

  • refref 函数用于创建一个包含响应式数据的引用对象,它接受一个基本类型或对象类型的参数,并返回一个具有 value 属性的对象。当访问或修改 value 属性时,会触发响应式更新。ref 函数会对对象类型的参数进行深度响应式转换,即递归地将对象的所有属性都转换为响应式的。
  • toReftoRef 函数用于创建一个指向另一个对象属性的响应式引用,它接受一个对象和一个属性名作为参数,并返回一个具有 value 属性的对象。当访问或修改 value 属性时,会同步地访问或修改原对象的属性,并触发响应式更新。toRef 函数不会对对象类型的属性进行深度响应式转换,即只会转换第一层属性。
  • toRefstoRefs 函数用于将一个响应式对象转换为一个普通对象,该普通对象的每个属性都是指向原对象相应属性的响应式引用。它接受一个响应式对象作为参数,并返回一个普通对象。当访问或修改普通对象的属性时,会同步地访问或修改原对象的属性,并触发响应式更新。toRefs 函数不会对对象类型的属性进行深度响应式转换,即只会转换第一层属性。
  • shallowRefshallowRef 函数用于创建一个浅层的响应式引用,它接受一个基本类型或对象类型的参数,并返回一个具有 value 属性的对象。当访问或修改 value 属性时,会触发响应式更新。shallowRef 函数不会对对象类型的参数进行深度响应式转换,即只会转换第一层属性。

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

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

相关文章

【数据结构】搜索树 与 Java集合框架中的Set,Map

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《JAVA数据结构》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力…

C# OpenCvSharp Yolov8 Face Landmarks 人脸特征检测

效果 项目 代码 using OpenCvSharp; using OpenCvSharp.Dnn; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms;namespace OpenCvSharp_Yolov8_Demo {public partial class frmMain…

C++数据结构X篇_25_堆排序(不稳定的排序)

本篇根据十大经典排序算法-堆排序算法详解进行整理和补充。 文章目录 1. 基础知识点1.1 完全二叉树1.2 堆的基础知识 2. 堆排序2.1 什么是堆排序2.2 算法原理2.2.1 理解方法12.2.2 理解方法2 2.3 算法实现 3. 堆排序算法特点3.1 时间复杂度3.2 空间复杂度3.3 稳定性 1. 基础知…

深度学习| U-Net网络

U-Net网络 基础知识和CNN的关系反卷积ReLU激活函数 U-Net入门U-Net网络结构图为什么需要跳跃连接U-Net的输入U-Net的应用 基础知识 理解U-Net网络结构需要相关知识点。 和CNN的关系 U-Net也是CNN&#xff08;Convolutional Neural Network&#xff0c;卷积神经网络&#xff…

网络架构学习1

文章目录 网络架构学习11. 传统CNN卷积神经网络1.1 基本思想1.2 VCG16(经典CNN网络架构之一) 2. 两种经典的网络架构2.1 FCN网络2.2 U-Net网络 3. FCNVMB(基于U-Net架构)3.1 FCNVMB 主要思想3.2 FCNVMB 提供的其他思想 网络架构学习1 1. 传统CNN卷积神经网络 1.1 基本思想 C…

Android SurfaceFlinger做Layer合成时,如何与HAL层进行交互

目录 零、本文讨论问题的范围一、问题&#xff1a;SurfaceFlinger图层合成选择实现方式的两难1.1 从OpenGL ES、HWC本身来讲1.2 以HWC为主导的判断逻辑 二、SurfaceFlinger与HAL层进行交互的具体实现框架2.1 SurfaceFlinger 调用 OpenGL ES 流程2.2 FrameBuffer2.3 SurfaceFlin…

c语言从入门到实战——数组

数组 前言1. 数组的概念2. 一维数组的创建和初始化2.1 数组创建2.2 数组的初始化2.3 数组的类型 3. 一维数组的使用3.1 数组下标3.2 数组元素的打印3.3 数组的输入 4. 一维数组在内存中的存储5. sizeof计算数组元素个数6. 二维数组的创建6.1 二维数组得概念6.2 二维数组的创建 …

Java集成腾讯云OCR身份证识别接口

一、背景 项目用到身份证识别获取人员信息的功能&#xff0c;于是想到了腾讯云提供这样的API。在整合代码过程都很顺利&#xff0c;利用腾讯云官方SDK很快集成进来。但是在上测试环境部署时有了新的问题&#xff0c;通过Nginx代理后的环境无法访问到目标腾讯云接口&#xff0c;…

buuctf_练[CSCCTF 2019 Qual]FlaskLight

[CSCCTF 2019 Qual]FlaskLight 文章目录 [CSCCTF 2019 Qual]FlaskLight掌握知识解题思路关键paylaod 掌握知识 内置函数的过滤&#xff0c;globals变量的过滤&#xff0c;调用内部变量或函数的OS函数进行命令执行 解题思路 打开题目链接&#xff0c;很明显看标题和内容是fla…

【动态基础】从暴力递归到动态规划

C面经汇总 系列综述&#xff1a; 目的&#xff1a;本系列是个人整理为了秋招和实习面试的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡背诵量与深入程度。 来源&#xff1a;材料主要源于算法大神&#xff08;左程云&#xff09;教你从暴力递归到动态规划进行的&#xf…

vue实现连接线

效果展示 实现代码 下载插件npm install --save leader-line-vue <template><div class"wrap"><div ref"start" class"start">start</div><div ref"end" class"end">end</div></d…

数据结构时间复杂度(补充)和空间复杂度

Hello&#xff0c;今天事10月27日&#xff0c;距离刚开始写博客已经过去挺久了&#xff0c;我也不知道是什么让我坚持这么久&#xff0c;但是学校的课真的很多&#xff0c;很少有时间多出来再学习&#xff0c;有些科目马上要考试了&#xff0c;我还不知道我呢不能过哈哈哈&…

Django 全局配置 settings 详解

文章目录 1 概述1.1 Django 目录结构 2 常用配置&#xff1a;settings.py2.1 注册 APP&#xff1a;INSTALLED_APPS2.2 模板路径&#xff1a;TEMPLATES2.3 静态文件&#xff1a;STATICFILES_DIRS2.4 数据库&#xff1a;DATABASES2.5 允许访问的主机&#xff1a;ALLOWED_HOSTS 1 …

[SQL开发笔记]UPDATE 语句:更新表中的记录

一、功能描述&#xff1a; UPDATE 语句&#xff1a;用于更新表中的记录 二、UPDATE 语句语法详解&#xff1a; UPDATE 语法 UPDATE table_nameSET column1value1,column2value2,...WHERE some_columnsome_value; 参数说明&#xff1a; 1.table_name&#xff1a;要修改的表…

【Docker】github Actions自动构建

通过github的Actions 实现代码push仓库后自动构建容器并发布到DockerHub. 创建项目 首先我们创建一个项目,这里我就用Vue项目进行演示. npm init vuelatest Actions-demo-swback进去项目&#xff0c;按照提示执行 npm install npm run dev 启动项目. 首先保证项目的正常启动…

DAY36 738.单调递增的数字 + 968.监控二叉树

738.单调递增的数字 题目要求&#xff1a;给定一个非负整数 N&#xff0c;找出小于或等于 N 的最大的整数&#xff0c;同时这个整数需要满足其各个位数上的数字是单调递增。 &#xff08;当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单…

Python机器学习基础(一)---数据集加载的方法

几个数据集加载的方式 鸢尾花练习资源(这个资源有瑕疵&#xff0c;index列和Species 都是带”“的字符串 导致一些加载现实问题&#xff0c;从而验证 还是pandas最好用) "index","Sepal.Length","Sepal.Width","Petal.Length","…

echarts中横向柱状图的数字在条纹上方

实现效果&#xff1a; 数字在条纹的上方 实现方法&#xff1a;这些数字是用新添加一个坐标轴来实现的 直接添加坐标轴数字显示是在条纹的正右边 所以需要配置一下偏移 完整代码 var option {grid: {left: "3%",right: "4%",bottom: "3%",cont…

FreeROTS 任务通知和实操 详解

目录 什么是任务通知&#xff1f; 任务通知值的更新方式 任务通知的优势和劣势 任务通知的优势 任务通知的劣势 任务通知相关 API 函数 1. 发送通知 2. 等待通知 任务通知实操 1. 模拟二值信号量 2. 模拟计数型信号量 3. 模拟事件标志组 4. 模拟消息邮箱 什么是任务…

高防CDN:网络攻防的坚强防线

在当今数字化时代&#xff0c;网络攻击已经成为一种常态&#xff0c;对企业和个人的网络资产构成了严重威胁。为了应对这些风险&#xff0c;高防CDN&#xff08;Content Delivery Network&#xff09;已经崭露头角&#xff0c;它不仅提供内容分发&#xff0c;还整合了强大的网络…