当前位置: 首页 > news >正文

【Vue3源码】第五章 ref的原理 实现ref

【Vue3源码】第五章 ref的原理 实现ref

上一章节我们实现了reactive 和 readonly 嵌套对象转换功能,以及shallowReadonly 和isProxy几个简单的API。

这一章我们开始实现 ref 及其它配套的isRef、unRef 和 proxyRefs

1、实现ref

接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value

ref 对象是可更改的,也就是说你可以为 .value 赋予新的值。它也是响应式的,即所有对 .value 的操作都将被追踪,并且写操作会触发与之相关的副作用。

如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。

单元测试代码

首先还是先看单元测试,我们测试代码去测试ref的功能是否齐全

在tests文件夹下新建一个ref.spec.ts文件

import { effect } from "../effect";
import { ref } from "../ref";
describe("ref", () => {it("should be reactive", () => {const a = ref(1);let dummy;let calls = 0;effect(() => {calls++;dummy = a.value;});expect(calls).toBe(1);expect(dummy).toBe(1);a.value = 2;expect(calls).toBe(2);expect(dummy).toBe(2);// same value should not triggera.value = 2;expect(calls).toBe(2);expect(dummy).toBe(2);});it("should make nested properties reactive", () => {const a = ref({count: 1,});let dummy;effect(() => {dummy = a.value.count;});expect(dummy).toBe(1);a.value.count = 2;expect(dummy).toBe(2);});
});

实现代码

ref其实就是为了给基础类型绑定响应式而创建的(如果是引用类型它会被转为reactive),它的原理和vue2的Object.defineProperty()类似。

同理的ref内也有和Proxy类似的get和set捕获器,不过ref主要是通过类实现的,所以ref的get和set叫类修饰符更贴切一点。他们运行的逻辑也很相似,都是在get时去收集依赖,set时触发依赖。

那么ref和reative原理最大区别是什么呢?

我们先看effect中的track依赖收集函数和trigger依赖触发函数:

在这里插入图片描述

是的不管track还是trigger两个函数他们都接收target和key两个参数,而ref我在上文说了它为了给基础类型绑定响应式而创建的。所以操作target和key的操作我们就可以省略了。

优化:

我们的ref在get时直接添加dep即可

//依赖收集
export function track(target, key) {if (activeEffect && shouldTrack) {let depsMap = targetMap.get(target);if (!depsMap) {depsMap = new Map();targetMap.set(target, depsMap);}let dep = depsMap.get(key);if (!dep) {dep = new Set();depsMap.set(key, dep);}trackEffects(dep)}
}export function trackEffects(dep) {if(dep.has(activeEffect)) returndep.add(activeEffect);//浅拷贝反向收集到depactiveEffect.deps.push(dep);
}

我们的trigger,也直接操作dep即可

//依赖触发
export function trigger(target, key) {let depsMap = targetMap.get(target);//用stop时所有的dep都被删了let dep = depsMap.get(key);triggerEffects(dep)
}
export function triggerEffects(dep) {for (let effect of dep) {// 当触发set时,如果有scheduler就执行schedulerif (effect.scheduler) {effect.scheduler();// 没有就触发ReactiveEffect实例的run方法} else {effect.run();}}
}

做完上面的操作我们已经抽离出了

  • 一个直接收集dep的trackEffects函数
  • 一个直接触发dep中依赖的triggerEffects函数

现在我们就可以封装ref函数了~

在reactivity文件夹下

新建ref.ts文件

import { hasChanged, isObject } from "../shared"
import { trackEffects, triggerEffects, activeEffect, shouldTrack } from "./effect"
import { reactive } from "./reactive"class RefImpl {private _value: any //劫持的数据public dep //收集depprivate _rawValue:any //劫持的数据备份constructor(value) {// 初始化 valuethis._rawValue = value// 如果是个对象就用reative包裹this._value = convert(value) // 初始化空的depthis.dep = new Set()}get value() {// 收集activeEffect实例trackRefValue(this)// 返回当前劫持的数据return this._value}set value(newValue) {// 要先修改value值再触发依赖// 如果修改过的值和初始化的值不相同if (hasChanged(newValue,this._rawValue)) {// 给初始化的值赋给成新的值this._rawValue = newValue// 如果是reactive对象也替换新值this._value = convert(newValue) // 去触发依赖triggerEffects(this.dep)}}
}function convert(value) {return isObject(value) ? reactive(value) : value
}function trackRefValue(ref) {if (activeEffect && shouldTrack) {trackEffects(ref.dep)}
}export function ref(value) {return new RefImpl(value)
}

shared文件夹下

index.ts文件

export const hasChanged = (val,newVal) => {return !Object.is(val,newVal)
}

2、实现isRef和unRef

isRef()

检查某个值是否为 ref。

unref()

如果参数是 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 计算的一个语法糖。

先看单测

it("isRef", () => {const a = ref(1);const user = reactive({age: 1,});expect(isRef(a)).toBe(true);expect(isRef(1)).toBe(false);expect(isRef(user)).toBe(false);});it.skip("unRef", () => {const a = ref(1);expect(unRef(a)).toBe(1);expect(unRef(1)).toBe(1);});

代码实现

实现起来非常的简单,我们怎么检查某个值是否为 ref呢?只要在RefImpl添加一个公共属性__v_isRef = true

这样我们就可以通过访问实例中ref.__v_isRef是否存在来鉴定某个值是否为 ref~

有了isRef,我们就可以实现unRef了,这是 val = isRef(val) ? val.value : val 计算的一个语法糖。

import { hasChanged, isObject } from "../shared"
import { trackEffects, triggerEffects, activeEffect, shouldTrack } from "./effect"
import { reactive } from "./reactive"class RefImpl {private _value: anypublic depprivate _rawValue:any//新增public __v_isRef =trueconstructor(value) {this._rawValue = valuethis._value = convert(value) this.dep = new Set()}get value() {trackRefValue(this)return this._value}set value(newValue) {// 要先修改value值再触发依赖if (hasChanged(newValue,this._rawValue)) {this._rawValue = newValuethis._value = convert(newValue) triggerEffects(this.dep)}}
}export function ref(value) {return new RefImpl(value)
}export function isRef(ref) {return !! ref.__v_isRef
}export function unRef(ref) {return isRef(ref) ? ref.value : ref
}

3、实现proxyRefs

看见proxyRefs你可能会觉得奇怪Vue3文档中并没有这个API,proxyRefs的功能主要是实现了不需要.value去访问ref对象。

先看单测

it("proxyRefs", () => {const user = {age: ref(10),name: "xiaohong",};const proxyUser = proxyRefs(user);expect(user.age.value).toBe(10);expect(proxyUser.age).toBe(10);expect(proxyUser.name).toBe("xiaohong");(proxyUser as any).age = 20;expect(proxyUser.age).toBe(20);expect(user.age.value).toBe(20);proxyUser.age = ref(20);expect(proxyUser.age).toBe(20);expect(user.age.value).toBe(20);});

实现代码

proxyRefs有什么用呢?

例如我们在template中访问ref对象时其实是不用.value去访问的。因为proxyRefs帮我们在get时做了处理让他返回成普通值,set时也做了处理让它返回普通值即可。

export function proxyRefs(objectWithRefs) {return isReactive(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs,{get(target,key) {// get 如果是ref类型那么就返回.value的值// 如果是普通的值直接返回return unRef(Reflect.get(target,key))},set(target,key,value) {// 判断旧值是不是ref,新值是ref还是普通类型if(isRef(target[key]) && !isRef(value)) {// 普通类型就替换成普通类型return target[key].value = value}else {// 是ref就返回.value的值return Reflect.set(target,key,value)}}})
}

下期分享:实现computed

相关文章:

【Vue3源码】第五章 ref的原理 实现ref

【Vue3源码】第五章 ref的原理 实现ref 上一章节我们实现了reactive 和 readonly 嵌套对象转换功能,以及shallowReadonly 和isProxy几个简单的API。 这一章我们开始实现 ref 及其它配套的isRef、unRef 和 proxyRefs 1、实现ref 接受一个内部值,返回一…...

[Flink]部署模式(看pdf上的放上面)

运行一个wordcountval dataStream: DataStream[String] environment.socketTextStream("hadoop1", 7777) //流式数据不能进行groupBy,流式数据要来一条处理一次.0表示第一个元素,1表示第二个元素 //keyBy(0)根据第一个元素进行分组 val out: DataStream[(String, In…...

Linux 查看 CPU 信息,机器型号,内存等信息

平时用的可能少,但需要记住,使用的命令,转载https://my.oschina.net/hunterli/blog/140783,以记录学习 系统 # uname -a # 查看内核/操作系统/CPU信息 # head -n 1 /etc/issue # 查看操作系统版本 # cat /proc/…...

三维量子力学 量子力学(3)

动量ppp有三个分量,为pxp_xpx​等。它们分别满足与位置坐标的对易关系,比如px−iℏ∂∂xp_x-i\hbar\frac{\partial }{\partial x}px​−iℏ∂x∂​。可以用位置坐标梯度算符表示即p−iℏ∇\bm{p}-i\hbar\nablap−iℏ∇。位置矢量用r\bm{r}r表示。 在d3r…...

Blazor入门100天 : 身份验证和授权 (6) - 使用 FreeSql orm 管理ids数据

目录 建立默认带身份验证 Blazor 程序角色/组件/特性/过程逻辑DB 改 Sqlite将自定义字段添加到用户表脚手架拉取IDS文件,本地化资源freesql 生成实体类,freesql 管理ids数据表初始化 Roles,freesql 外键 > 导航属性完善 freesql 和 bb 特性 本节源码 https://github.com/…...

Java文件IO操作:File类的相关内容

Java文件IO操作一、File类1.相对路径和绝对路径2.路径分隔符(同一路径下、多个路径下)3.实例化4.常见方法一、File类 File类继承自Object类,实现了Serializable接口和Comparable接口; File类属于java.io包; File类是文…...

竣达技术 | 巡检触摸屏配合电池柜,电池安全放首位!

机房蓄电池常见的故障 1.机房电池着火和爆炸 目前在数据机房蓄电池爆炸着火事故频发,导致业主损失严重。一般机房电池是由于其中一节电池裂化后未妥善管理,电池急剧恶化导致爆炸着火。由于电池是串联及并联在使用,只要一节着火燃烧整片瞬间…...

什么是自动化运维?为什么选择Python做自动化运维?

“Python自动化运维”这个词,想必大家都听说过,但是很多人对它并不了解,也不知道是做什么的,那么你对Python自动化运维了解多少呢?跟着蛋糕往下看。 什么是Python自动化运维? 随着技术的进步、业务需求的快速增长,…...

【经验】移植环境requirement时报错

问题描述 在使用pip freeze > ./requirements.txt和pip install -r requirement.txt (requirements.txt文件用来记录当前程序的所有依赖包及其精确版本号)从一台电脑移植到另一台电脑的 conda 环境时,出现了一堆类似的报错: E…...

计算机专业要考什么证书?

大家好,我是良许。 从去年 12 月开始,我已经在视频号、抖音等主流视频平台上连续更新视频到现在,并得到了不错的评价。 视频 100% 原创录制,绝非垃圾搬运号,每个视频都花了很多时间精力用心制作,欢迎大家…...

一个列表引发的思考(简单版)

最近老板让我按照设计图写一个页面,不嫌丢人的说这是我第一次写页面,哈哈哈。 然后设计图里有一个这样的需求,感觉挺有意思的。 为什么感觉有意思呢,因为这个列表它前面是图片,然后单行和双行的不一样。(请…...

Protobuf 学习简记(三)Unity C#中的序列化与反序列化

Protobuf 学习简记(三)Unity C#中的序列化与反序列化对文本的序列化与反序列化内存二进制流的序列化与反序列化方法一方法二参考链接对文本的序列化与反序列化 private void Text() {TestMsg1 myTestMsg new TestMsg1();myTestMsg.TestInt32 1;myTest…...

Flask入门(10):Flask使用SQLAlchemy

目录11.SQLAlchemy11.1 简介11.2 安装11.3 基本使用11.4 连接11.5 数据类型11.6 执行原生sql11.7 插入数据11. 8 删改操作11.9 查询11.SQLAlchemy 11.1 简介 SQLAlchemy的是Python的SQL工具包和对象关系映射,给应用程序开发者提供SQL的强大功能和灵活性。它提供了…...

我的 System Verilog 学习记录(4)

引言 本文简单介绍 System Verilog 语言的 数据类型。 前文链接: 我的 System Verilog 学习记录(1) 我的 System Verilog 学习记录(2) 我的 System Verilog 学习记录(3) 数据类型简介 Sys…...

Git : 本地分支与远程分支的映射关系

概述 本文介绍 git 环境中本地分支与远程分支的映射关系的查看和调整。 1、查看本地分支与远程分支的映射关系 执行如下命令: git branch -vv注意就是两个 v ,没有写错。 可以获得分支映射结果: dev fa***** [github/dev] update * main…...

运维必看|跨国公司几千员工稳定访问Office365,怎么实现?

【客户背景】本次分享的客户是全球传感器领域的领导者,其核心产品为电流和电压传感器,被广泛应用于驱动和焊接、可再利用能源以及电源、牵引、高精度、传统和新能源汽车等领域。 作为一家中等规模的全球化公司,该公司在北京、日本、西欧、东欧…...

Python GDAL读取栅格数据并基于质量评估波段QA对指定数据加以筛选掩膜

本文介绍基于Python语言中gdal模块,对遥感影像数据进行栅格读取与计算,同时基于QA波段对像元加以筛选、掩膜的操作。本文所要实现的需求具体为:现有自行计算的全球叶面积指数(LAI).tif格式栅格产品(下称“自…...

Vue3:有关v-model的用法

目录 前言: 回忆基本的原生用法: 原生input的封装: 自定义v-model参数: 对el-input的二次封装: 多个v-model进行绑定: v-model修饰符: v-model自定义参数与自定义修饰符的结合: 前言&am…...

CF1692C Where‘s the Bishop? 题解

CF1692C Wheres the Bishop? 题解题目链接字面描述题面翻译题目描述题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示代码实现题目 链接 https://www.luogu.com.cn/problem/CF1692C 字面描述 题面翻译 题目描述 有一个888\times888的棋盘,列编号从…...

Jenkins集成Allure报告

Jenkins集成Allure报告 紧接上文:Jenkins部署及持续集成——傻瓜式教程 使用Allure报告 1、在插件库下载Allure插件Allure Jenkins Plugin 2、在构建后操作中加入allure执行的报告目录(相对于项目的路径) 3、run.py代码改成如下 import p…...

压测不只是加并发:我们模拟真实用户行为后,发现了隐藏瓶颈

在性能测试领域,一个根深蒂固的误解是:压测就是使劲加线程数,看系统什么时候崩。很多团队用 JMeter 起 500 个并发,照着接口列表跑一圈,看到 TPS 平稳、响应时间没超过 300ms,就觉得万事大吉。可一旦上线&a…...

5分钟上手G-Helper:彻底告别Armoury Crate臃肿的终极指南

5分钟上手G-Helper:彻底告别Armoury Crate臃肿的终极指南 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, Zenbook,…...

哔哩漫游X:全面解锁B站功能的终极ReVanced增强模块

哔哩漫游X:全面解锁B站功能的终极ReVanced增强模块 【免费下载链接】BiliRoamingX-integrations BiliRoamingX integrations and patches powered by ReVanced. 项目地址: https://gitcode.com/gh_mirrors/bi/BiliRoamingX-integrations B站作为中国最大的视…...

为什么iOS越狱依然充满魔力?揭秘现代越狱技术的创新突破

为什么iOS越狱依然充满魔力?揭秘现代越狱技术的创新突破 【免费下载链接】Jailbreak iOS 26.4 - 26, 17 - 17.7.5 & iOS 18 - 18.7.3 Jailbreak Tools, Cydia/Sileo/Zebra Tweaks & Jailbreak News Updates || AI Jailbreak Finder 👇 项目地址…...

Construct3新手避坑指南:用《幽灵射手》教程搞定你的第一个射击游戏(附B站效果演示)

Construct3新手避坑指南:用《幽灵射手》教程搞定你的第一个射击游戏第一次打开Construct3的《幽灵射手》教程时,我盯着满屏的绿色幽灵和事件表发呆了半小时。为什么子弹穿过了幽灵却没造成伤害?为什么游戏运行三秒后就卡成幻灯片?…...

UE动画师避坑指南:状态机(State Machine)乱成一团麻?试试这3个整理技巧和最佳实践

UE动画师高效工作指南:状态机结构化管理的3个核心策略当项目进入中后期开发阶段,动画蓝图的状态机往往会变成一团纠缠不清的"意大利面条"。每次添加新功能都像是在已经混乱的线团上再打一个结,最终导致团队协作效率直线下降。我曾参…...

Codex适配国产信创环境安装部署与技术适配全解析

随着国家信创产业持续落地推进,党政、金融、能源、工业等关键行业全面开启信息技术软硬件国产化替代工作。基于自主可控、安全可信的核心需求,传统国外架构软硬件体系逐步被国产操作系统、国产芯片硬件替代。Codex作为主流的智能代码辅助、自动化开发工具…...

Rockwell Studio5000 IO模块

一.型号命名规则: I:Input 输入模块 IA/IB/IC/IG/IH/IM/IN/IV O: Output 输出模块 A: AC 交流 B: DC 直流 I:Isolated 电气隔离,抗干扰强 D:Diagnostic 诊断功能,支持断线/故障诊断 F:High Speed 高速输…...

自动售货机(设计源文件+万字报告+讲解)(支持资料、图片参考_降重降ai)_文章底部可以扫码

摘 要 自动售货机的应用,不仅可以充分节省人力资源,而且还促进商业贸易发展,给人们的生活带来诸多便利。可编程控制器作为控制系统的大脑,按照工艺说明分析,对各种外部输入信号按照系统的工艺分析结果及程序设计流程&…...

Unity版本选择避坑指南:LTS稳定幻觉与个人版合规雷区

1. 为什么Unity版本选择不是“装最新版就完事”?刚接触Unity的新手,十有八九会直接去官网下载那个醒目的“Download Latest Version”按钮——毕竟谁不想用上最酷的HDRP、最顺的DOTS、最全的AI工具链?我带过三届Unity训练营,每届都…...