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

前言

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 函数不会对对象类型的参数进行深度响应式转换,即只会转换第一层属性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一文搞懂 MineCraft 服务器启动操作和常见问题 2023年10月

文章目录 前言1. 新建文件夹2. 创建 bat 文件3. 编辑 bat 文件4. 启动服务器5. 恭喜完成 文章持续更新中&#xff0c;如果你有问题可以通过 qq 1317699264 获取免费协助&#xff0c;解决的问题将会被更新到本文章中 前言 无论你是使用服务端整合包&#xff0c;还是从上一篇我的…

基本选择器

目录 1 标签选择器 2 类选择器 3 id选择器 作用&#xff1a;选择页面上的某一个或某一类元素 1 标签选择器 标签选择器会选择页面上所有的标签 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>…

Spigot 通过 BuildTools 构建 MineCraft Spigot 官方服务端文件

文章目录 从 Spigot 官方下载 BuildTools spigotmc / buildtools确保你有正确版本的 Java&#xff08;例如构建 1.20.2 的服务端一般需要有 Java17&#xff09;在 BuildTools.jar 同名文件夹打开 cmd 命令行&#xff08;点击红色圈圈区域输入 cmd 按 enter 即可&#xff09; …

【黑产攻防道03】利用JS参数更新检测黑产的协议破解

任何业务在运营一段时间之后都会面临黑产大量的破解。验证码和各种爬虫的关系就像猫和老鼠一样, 会永远持续地进行博弈。极验根据十一年和黑产博弈对抗的经验&#xff0c;将黑产的破解方式分为三类&#xff1a; 1.通过识别出验证码图片答案实现批量破解验证&#xff0c;即图片…

区块链技术的未来:去中心化应用和NFT的崛起

区块链技术正在以前所未有的速度改变着金融和数字资产领域。它的演进为去中心化应用和非替代性代币&#xff08;NFT&#xff09;的崛起提供了坚实的基础。在本文中&#xff0c;我们将深入探讨这一数字革命的关键方面&#xff0c;从区块链的基本原理到它如何改变金融领域&#x…

使用Jetpack Compose构建Flappy Musketeer街机游戏

使用Jetpack Compose构建Flappy Musketeer街机游戏 一步一步创建沉浸式移动游戏的指南 引言 Flappy Musketeer不仅是又一个移动游戏&#xff1b;它将令人上瘾的“轻点飞行”游戏玩法和引人入胜的视觉效果融合在一起&#xff0c;吸引玩家进入埃隆马斯克&#xff08;Elon Musk…

Transformer英语-法语机器翻译实例

依照Transformer结构来实例化编码器&#xff0d;解码器模型。在这里&#xff0c;指定Transformer编码器和解码器都是2层&#xff0c;都使用4头注意力。为了进行序列到序列的学习&#xff0c;我们在英语-法语机器翻译数据集上训练Transformer模型&#xff0c;如图11.2所示。 da…

网络协议--DNS:域名系统

14.1 引言 域名系统&#xff08;DNS&#xff09;是一种用于TCP/IP应用程序的分布式数据库&#xff0c;它提供主机名字和IP地址之间的转换及有关电子邮件的选路信息。这里提到的分布式是指在Internet上的单个站点不能拥有所有的信息。每个站点&#xff08;如大学中的系、校园、…

工业4.0的安全挑战与解决方案

在当今数字化时代&#xff0c;工业4.0已经成为制造业的核心趋势。工业4.0的兴起为生产企业带来了前所未有的效率和灵活性&#xff0c;但与之伴随而来的是一系列的安全挑战。本文将深入探讨工业4.0的安全挑战&#xff0c;并提供一些解决方案&#xff0c;以确保制造业的数字化转型…

Typora(morkdown编辑器)的安装包和安装教程

Typora&#xff08;morkdown编辑器&#xff09;的安装包和安装教程 下载安装1、覆盖文件2、输入序列号①打开 typora &#xff0c;点击“输入序列号”&#xff1a;②邮箱一栏中任意填写&#xff08;但须保证邮箱地址格式正确&#xff09;&#xff0c;输入序列号&#xff0c;点击…

JS中面向对象的程序设计

面向对象&#xff08;Object-Oriented&#xff0c;OO&#xff09;的语言有一个标志&#xff0c;那就是它们都有类的概念&#xff0c;而通过类可以创建任意多个具有相同属性和方法的对象。但在ECMAScript 中没有类的概念&#xff0c;因此它的对象也与基于类的语言中的对象有所不…

order by数据过多引起的cpu飙升

测试环境 1.目前数据库类型为pg数据库2.目前数据库业务为共享数据库,为减少其他业务对本次测试的影响,故选在业务空闲时间执行3.服务器性能为8C 32GB 500GB硬盘 原程序测试结果 优化后程序结果 出现原因 当数据量大时&#xff0c;order by排序操作会消耗大量的CPU资源&#…

数据结构之队列

什么是队列&#xff1f; 队列这个概念非常好理解。你可以把它想象成排队买票&#xff0c;先来的先买&#xff0c;后来的人只能站末尾&#xff0c;不允许插队。先进者先出&#xff0c;这就是典型的“队列”。 我们知道&#xff0c;栈只支持两个基本操作&#xff1a;入栈 push()…

Spring Session框架

Spring Session框架 前言 Spring Session是一个用于在分布式环境中管理会话的框架。它提供了一种无状态的方式来管理用户会话&#xff0c;使得应用程序可以在不同的服务器之间共享会话数据。Spring Session的设计目标是为了解决传统基于Servlet容器的会话管理的局限性&#xf…

【ARM 嵌入式 C 入门及渐进 10 -- 冒泡排序 选择排序 插入排序 快速排序 归并排序 堆排序 比较介绍】

文章目录 排序算法小结排序算法C实现 排序算法小结 C语言中常用的排序算法包括冒泡排序、选择排序、插入排序、快速排序、归并排序、堆排序。下面我们来一一介绍&#xff1a; 冒泡排序&#xff08;Bubble Sort&#xff09;&#xff1a;冒泡排序是通过比较相邻元素的大小进行排…

(el-Table)操作(不使用 ts):Element-plus 中 Table 多选框的样式等的调整

Ⅰ、Element-plus 提供的 Table 表格组件与想要目标情况的对比&#xff1a; 1、Element-plus 提供 Table 组件情况&#xff1a; 其一、Element-ui 自提供的 Table 代码情况为(示例的代码)&#xff1a; // Element-plus 自提供的代码&#xff1a; // 此时是使用了 ts 语言环境…

大语言模型(LLM)综述(四):如何适应预训练后的大语言模型

A Survey of Large Language Models 前言5. ADAPTATION OF LLMS5.1 指导调优5.1.1 格式化实例构建5.1.2 指导调优策略5.1.3 指导调优的效果5.1.4 指导调优的实证分析 5.2 对齐调优5.2.1 Alignment的背景和标准5.2.2 收集人类反馈5.2.3 根据人类反馈进行强化学习5.2.4 无需 RLHF…

Leetcode. 2866.美丽塔II

要求O&#xff08;N&#xff09;复杂度内解决&#xff0c;考虑单调栈&#xff0c;这个题很像经典的美丽度的那个单调栈的模板题 对有每一个位置&#xff0c;考虑右边能扩展到哪来&#xff1f;不如直接从末尾来倒着看&#xff0c;发现从末尾需要维护一个单调增的单调栈&#xff…

前端实现打印功能Print.js

前端实现打印的方式有很多种&#xff0c;本人恰好经历了几个项目都涉及到了前端打印&#xff0c;目前较为推荐Print.js来实现前端打印 话不多说&#xff0c;直接上教程 官方链接: Print.js官网 在项目中如何下载Print.js 使用npm下载&#xff1a;npm install print-js --sav…

机器视觉3D项目评估的基本要素及测量案例分析

目录 一. 检测需求确认 1、产品名称&#xff1a;【了解是什么产品上的零件&#xff0c;功能是什么】 2、*产品尺寸&#xff1a;【最大兼容尺寸】 3、*测量项目&#xff1a;【确认清楚测量点位】 4、*精度要求&#xff1a;【若客户提出的精度值过大或者过小&#xff0c;可以和客…