草庐IT

【源码】Vue3 Ref原理,套娃的艺术

ceido 2023-03-28 原文

前言

Vue3有个Ref API,官网文档 说明其主要的用处是:1、将一个原始类型值 (例如,一个字符串),变成响应式的。2、当解构的两个 property 的响应性都会丢失时,可以将我们的响应式对象转换为一组 ref。这些 ref 将保留与源对象的响应式关联。
下面是对应的两个例子:

import { ref } from 'vue'

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1
import { reactive, toRefs } from 'vue'

const book = reactive({
  author: 'Vue Team',
  title: 'Vue 3 Guide',
})

let { author, title } = toRefs(book)

title.value = 'Vue 3 Detailed Guide' // 我们需要使用 .value 作为标题,现在是 ref
console.log(book.title) // 'Vue 3 Detailed Guide'

由于 Proxy 只能代理对象,那么Vue3是如何代理原始类型值的呢?
Ref 源码在 packages/reactivity/src/ref.ts 下,阅读版本是v3.2.30ref.ts只有200多行代码。看一下 Ref 是怎么实现的。

正文

(1)Ref

找到ref函数,其调用路径是 ref -> createRef -> new RefImplRefImpl是一个类,其实现很简单,提供一个value属性和value的访问器getset

class RefImpl<T> {
  private _value: T

  public readonly __v_isRef = true

  constructor(private _rawValue: T, public readonly _shallow = false) {
    this._value = _shallow ? _rawValue : convert(_rawValue)
  }

  get value() {
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }

  set value(newVal) {
    if (hasChanged(toRaw(newVal), this._rawValue)) {
      this._rawValue = newVal
      this._value = this._shallow ? newVal : convert(newVal)
      trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
    }
  }
}

也就是说,ref的实现,还是使用对象,将原始类型值包一层,存到对象的value属性上。这也是为什么需要用 count.value 来访问和设置属性值。最终还是要使用对象的访问器get来收集依赖(track)、set来触发更新(trigger)。我们可以自己简单实现一个ref:

image.png

打开 Vue SFC Playground: ref 可查看和运行上面的例子。

(2)toRef、toRefs

接下来看看ref.tstoReftoRefs的实现,查看这两个API的使用,发现其就是使用 ref 与源响应式对象字段的响应式关联起来。其解决的问题是,当响应式对象的属性解构到新的普通对象newObj时,响应式会丢失:

const book = reactive({
  author: 'Vue Team',
  title: 'Vue 3 Guide',
})

const newObj = {
  ...book
}

newObj.author // 收集不到依赖
newObj.author = "Team" // 触发不了更新

下面看看toRef、toRefs:

image.png

他们的实现很简单,和实现ref的原理一样,也是包一层,提供一个value字段。当getset这个ref.value时,操作的是源响应式对象。达到与源响应式对象字段的响应式关联的目的,从而解决响应式丢失问题。
打开 Vue SFC Playground: toRef、toRefs 可查看自己实现的toRef、toRefs。
image.png

(3)ref.value问题与proxyRefs

当我们使用ref时,很多人吐槽,我们总是要加一个.value,用起来太不爽了。但我们发现,我们在模板中使用ref的时候,并不需要.value,那它是怎么实现的呢?

ref.ts中还有一个 proxyRefs :

image.png

可以看到,它就是对包含 ref类型字段的对象再包一层,把普通对象newObj也转成代理对象!当访问 newObj.foo时,判断newObj.foo是不是一个ref,如果是则代理到newObj.foo.value

然后我们搜一下proxyRefs在哪里有使用,可以看到在packages/runtime-core/src/component.ts中,handleSetupResult 会将 setupResult 传入 proxyRefs!这就是我们模板中使用 ref 对象(newObj.foo)不用加.value的原因。

image.png

至此,模板中的使用问题解决了。但我们还有个问题是,在script中使用还是需要.value:

function add() {
    newObj.foo.value++
}

对于这个问题,尤大提了一个提案 Ref Sugar 的 RFC,即 ref 语法糖,目前还处理实验性的(Experimental)阶段。提案的主要内容是尤大想提供一个语法糖$ref。使用$ref,编译器编译时会自动加上.value,这样我们编写代码时就不用使用.value了:

import { $ref } from 'vue'

const count = $ref(0)
console.log(count) // 0

count++
console.log(count) // 1

我们通过 Vue Playground: $ref 可以直观地感受一下。

总结

至此,ref的源码也就看得差不多了,可以看到这是一个多重代理的东西。如果我们在模板中使用一个ref属性(newObj.foo),看看其经过的代理:
newObj.foo -> newObj.foo.value -> reactiveObj.foo -> rawObj.foo

其中rawObj是我们最初的普通对象。所以标题说这真是套娃。

参考

Vue3 Ref 语法糖,告别 .value 的写法

有关【源码】Vue3 Ref原理,套娃的艺术的更多相关文章

  1. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  2. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  3. ruby - 是否有将图像文件转换为 ASCII 艺术的命令行程序或库? - 2

    有这样的事吗?我想在Ruby程序中使用它。 最佳答案 试试这个http://csl.sublevel3.org/jp2a/此外,Imagemagick可能还有一些东西 关于ruby-是否有将图像文件转换为ASCII艺术的命令行程序或库?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/6510445/

  4. elasticsearch源码关于TransportSearchAction【阶段三】 - 2

    1.回顾.TransportServicepublicclassTransportServiceextendsAbstractLifecycleComponentTransportService:方法:1publicfinalTextendsTransportResponse>voidsendRequest(finalTransport.Connectionconnection,finalStringaction,finalTransportRequestrequest,finalTransportRequestOptionsoptions,TransportResponseHandlerT>

  5. (附源码)vue3.0+.NET6实现聊天室(实时聊天SignalR) - 2

    参考文章搭建文章gitte源码在线体验可以注册两个号来测试演示图:一.整体介绍  介绍SignalR一种通讯模型Hub(中心模型,或者叫集线器模型),调用这个模型写好的方法,去发送消息。  内容有:    ①:Hub模型的方法介绍    ②:服务器端代码介绍    ③:前端vue3安装并调用后端方法    ④:聊天室样例整体流程:1、进入网站->调用连接SignalR的方法2、与好友发送消息->调用SignalR的自定义方法 前端通过,signalR内置方法.invoke()  去请求接口3、监听接受方法(渲染消息)通过new signalR.HubConnectionBuilder().on

  6. 【Unity游戏破解】外挂原理分析 - 2

    文章目录认识unity打包目录结构游戏逆向流程Unity游戏攻击面可被攻击原因mono的打包建议方案锁血飞天无限金币攻击力翻倍以上统称内存挂透视自瞄压枪瞬移内购破解Unity游戏防御开发时注意数据安全接入第三方反作弊系统外挂检测思路狠人自爆实战查看目录结构用il2cppdumper例子2-森林whoishe后记认识unity打包目录结构dll一般很大,因为里面是所有的游戏功能编译成的二进制码游戏逆向流程开发人员代码被编译打包到GameAssembly.dll中使用il2ppDumper工具,并借助游戏名_Data\il2cpp_data\Metadata\global-metadata.dat

  7. Cesium源码解析一(terrain文件的加载、解析与渲染全过程梳理) - 2

    快速导航(持续更新中…)Cesium源码解析一(terrain文件的加载、解析与渲染全过程梳理)Cesium源码解析二(metadataAvailability的含义)Cesium源码解析三(metadata元数据拓展中行列号的分块规则解析)Cesium源码解析四(Quantized-Mesh(.terrain)格式文件在CesiumJS和UE中加载情况的对比)目录1.前言2.本篇的由来3.terrain文件的加载3.1更新环境3.2更新和执行渲染命令3.3数据优化3.4结束当前帧4.总结1.前言  目前市场上三维比较火的实现方案主要有两种,b/s的方案主要是Cesium,c/s的方案主要是u

  8. vue 实现内容超出两行显示展开更多功能,可依据需求自定义任意行数! - 2

    平时开发中我们经常会遇到这样的需求,在一个不限高度的盒子中会有很多内容,如果全部显示用户体验会非常不好,所以可以先折叠起来,当内容达到一定高度时,显示展开更多按钮,点击即可显示全部内容,先来看看效果图: 这样做用户体验瞬间得到提升,接下来看看具体细节。0">主要操作在内容这里{{item.username}},……展开更多样式大家可依据自己项目需求进行设计,这里就不贴了,主要说几个关键的。1、在data中定义三个属性isShowMore:false, //控制展开更多的显示与隐藏textHeight:null, //框中内容的高度status:false, //内容状态是否打开2.计算内容是否

  9. Slowloris DoS攻击的原理与简单实现 - 2

    前言    Slowloris攻击是我在李华峰老师的书——《MetasploitWeb 渗透测试实战》里面看的,感觉既简单又使用,现在这种攻击是很容易被防护的啦。不过我也不敢真刀实战的去试,只是拿个靶机玩玩罢了。         废话还是写在结语里面吧。(划掉)结语可以不看(划掉)Slowloris攻击的原理        Slowloris是一种资源消耗类DoS攻击,它利用部分HTTP请求进行操作。也叫做慢速攻击,这里的慢速并不是说发动攻击慢,而是访问一条链接的速度慢。Slowloris攻击的功能是打开与目标Web服务器的连接,然后尽可能长时间的保持这些连接打开。如果由多台电脑同时发起Slo

  10. vue3.0 + vite2.0+如何兼容低版本浏览器 - 2

    这里写自定义目录标题一、问题二、解决三、解决方案四、打包预览一、问题在使用vue3.2和vite2开发一个移动端或者钉钉端H5微服务iosapp内置浏览器打开没问题安卓app内置浏览器打开空白页面vconsole打印出现报错globalthisundefind二、解决内置浏览器版本比较低打印出来是63vue3代码不兼容低版本浏览器三、解决方案步骤一:vite.config.ts里build.target配置项指定构建目标为es2015或者步骤二:安装@vitejs/plugin-legacy安装完报错也还在指定版本可以解决“@vitejs/plugin-legacy”:“1.8.0”,步骤三:

随机推荐