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

Vue3源码reactivity响应式篇之Ref

概览

vue3中ref用于将原始数据(如布尔值、数字、字符串等)包裹成为响应式数据。具体实现的文件参见packages\reactivity\src\ref.ts与之相关的有如下api

  • ref: 用于创建响应式数据,当数据发生变化时,会触发视图更新。
  • shallowRef: 用于创建浅层响应式数据,只有数据重新被赋值时,才会触发视图更新。
  • isRef: 用于判断一个数据是否是ref响应式数据。
  • toRef: 用于将一个响应式对象的属性或某数据转换为ref响应式数据。
  • toValue:
  • toRefs:将响应式对象的所有属性转换为ref响应式数据
  • unref: 解绑响应式数据,返回原始数据
  • proxyRefs: 用于为包含ref的对象创建代理
  • customRef: 自定义ref,用于创建一个自定义的ref响应式数据
  • triggerRef:强制触发ref的更新

源码分析

ref

ref的实现如下:

function ref(value) {return createRef(value, false);
}

ref的实现就是一个高阶函数,会返回调用createRef函数的结果。此时createRef接受的第二个参数shallowfalse,表示ref创建的响应式数据不是一个浅层响应。

createRef

createRef函数顾名思义就是创建一个响应式数据,其实现如下:

function createRef(rawValue, shallow) {if (isRef(rawValue)) {return rawValue;}return new RefImpl(rawValue, shallow);
}

createRef函数接受两个参数,第一个参数rawValue表示原始数据,第二个参数shallow表示是否是浅层响应。首先会调用isRef(后面会讲到isRef的实现)判断原始数据rawValue是不是Ref响应式数据,若是,则直接返回该值;否则实例化RefImpl类,并返回实例对象。

RefImpl

RefImpl类的实现如下:

class RefImpl {constructor(value, __v_isShallow) {this.__v_isShallow = __v_isShallow; //是否是浅层响应的标志位__publicField(this, "_value"); // 响应式数据的当前值__publicField(this, "_rawValue"); // 原始数据__publicField(this, "dep",new Dep()); // 依赖收集器__publicField(this, "__v_isRef", true); // 标志位,用于判断是否是 Ref 响应式数据this._rawValue = __v_isShallow ? value : toRaw(value);this._value = __v_isShallow ? value : toReactive(value);}get value() {{this.dep.track({target: this,type: "get",key: "value"});}return this._value;}set value(newVal) {const oldValue = this._rawValue;const useDirectValue = this["__v_isShallow"] || isShallow(newValue) || isReadonly(newValue);newValue = useDirectValue ? newValue : toRaw(newValue);if (hasChanged(newValue, oldValue)) {this._rawValue = newValue;this._value = useDirectValue ? newValue : toReactive(newValue);{this.dep.trigger({target: this,type: "set",key: "value",newValue,oldValue});}}}
}

RefImpl类在构造函数中会根据参数__v_isShallow判断是否是浅层响应,若是,则直接将参数value赋值给实例的_rawValue_value;否则,会调用toRaw函数将原始数据转换为原始值,再调用toReactive函数将原始值转换为响应式数据,最后将转换后的数据赋值给实例的_rawValue_value

RefImpl类还定义了get value()set value(newVal)方法,用于获取和设置响应式数据的值。

get value()方法中,会调用trackRefValue函数进行依赖收集,然后返回实例的_value属性;
set value(newVal)方法中,会先判断是否直接使用新值,若当前实例是浅层响应(__v_isShallowtrue)或者新值的__v_isReadonly__v_isShallow属性为true,则直接使用新值,否则调用toRaw获取新值的原始值;然后调用hasChanged方法比较新值和实例的原始值是否相等,hasChanged的内部就是通过Object.is进行比较;若不等,则更新实例的_rawValue_value;最后调用triggerRefValue方法通知所有依赖于实例的副作用进行更新,该方法后续会介绍。

toRaw

toRaw的作用就是获取响应式数据的原始值,会返回参数observed__v_raw的值,若该值不存在,则返回参数observed

function toRaw(observed) {const raw = observed && observed["__v_raw"];return raw ? toRaw(raw) : observed;
}

toRaw实际上是个递归函数,直到返回原始值为止。对于reactive响应式数据而言,读取它的__v_raw,会返回原始值,在BaseReactiveHandler类中的get实现的。

toReactive

前面提到ref会将原始数据转换为响应式数据,而如果ref接受的是一个对象,则返回的也是响应式对象,这就是在toReactive中实现的。

toReactive的实现如下:

const toReactive = (value) => isObject(value) ? reactive(value) : value;

toReactive会判断参数是否是对象,若不是,则直接返回参数;否则,会调用reactive函数将参数转换为响应式对象,并返回该对象。所以若将对象作为参数传给ref,响应式实际上通过reactive函数实现的。

shallowRef

shallowRef的实现如下:

function shallowRef(value) {return createRef(value, true);
}

shallowRef的实现和ref的实现类似,唯一的不同就是调用createRef时,第二个参数是true,表示创建的响应式数据是一个浅层响应式数据。

isRef

isRef的实现如下:

function isRef(r) {return r ? r["__v_isRef"] === true : false;
}

isRef实际就是读取参数r__v_isRef属性,若该属性为true,则返回true,否则返回false。因为在创建ref响应式数据时,实例的__v_isRef都会设置为true

toRef

function toRef(source, key, defaultValue) {if (isRef(source)) {return source;} else if (isFunction(source)) {return new GetterRefImpl(source);} else if (isObject(source) && arguments.length > 1) {return propertyToRef(source, key, defaultValue);} else {return ref(source);}
}

toRef的源码实现实际上是用了typescript的重载,参数会有以下几种情况:

  • 第一个参数是ref响应式数据,直接返回该数据
  • 第一个参数是函数,返回一个GetterRefImpl实例
  • 第一个参数是对象,第二个参数是属性名,返回一个PropertyRefImpl实例
  • 其他情况,返回一个RefImpl实例
GetterRefImpl

GetterRefImpl的实现如下:

class GetterRefImpl {constructor(_getter) {this._getter = _getter;__publicField(this, "__v_isRef", true);__publicField(this, "__v_isReadonly", true);__publicField$1(this, "_value");}get value() {return this._value = this._getter();}
}

_getter实际上就是toRef中的第一个参数,它是一个函数,用于获取响应式数据的值,除此之外GetterRefImpl类还会在构造函数中设置实例__v_isRef__v_isReadonlytrue(表示是一个ref响应式数据且只读,因为实例没有setter的实现)

propertyToRef

propertyToRef的作用就是将对象的属性转换为ref响应式数据,它的实现如下:

function propertyToRef(source, key, defaultValue) {const val = source[key];return isRef(val) ? val : new ObjectRefImpl(source, key, defaultValue);
}

propertyToRef接受三个参数:对象source、属性名key和默认值defaultValue;会先通过isRef判断值是否是响应式数据,若是,则直接返回;否则会实例化ObjectRefImpl类,并返回实例。

ObjectRefImpl

ObjectRefImpl的实现如下:

class ObjectRefImpl {constructor(_object, _key, _defaultValue) {this._object = _object;this._key = _key;this._defaultValue = _defaultValue;__publicField(this, "__v_isRef", true);__publicField$1(this, "_value");}get value() {const val = this._object[this._key];return this._value = val === void 0 ? this._defaultValue : val;}set value(newVal) {this._object[this._key] = newVal;}get dep() {return getDepFromReactive(toRaw(this._object), this._key);}
}

ObjectRefImpl类用于将响应式对象的一个属性作为独立的ref使用。

toValue

toValue的作用就是将响应式数据转换为普通数据,它的实现如下:

function toValue(source) {return isFunction(source) ? source() : unref(source);
}

toValue的参数source可以是一个getter函数或者响应式数据,若是函数,则调用该函数并返回函数的返回值;否则,调用unref函数并返回unref函数的返回值。

toRefs

toRefs的作用就是将响应式对象的所有属性转换为ref响应式数据,它的实现如下:

function toRefs(object) {const ret = isArray(object) ? new Array(object.length) : {};for (const key in object) {ret[key] = propertyToRef(object, key);}return ret;
}

toRefs的参数object可以是一个数组或者对象,若为数组,则返回一个数组,数组的每个元素都是一个ref响应式数据;若为对象,则返回一个对象,对象的每个属性都是一个ref响应式数据。

unref

unref的实现如下:

function unref(ref2) {return isRef(ref2) ? ref2.value : ref2;
}

unref会先调用isRef判断参数是否是响应式对象,若为响应式对象,则返回该对象的value属性;否则,直接返回参数。

proxyRef

proxyRef的相关实现如下

const shallowUnwrapHandlers = {// 当访问代理对象的属性时会触发,使用`Reflect.get`获取属性值,再使用`unref`解包get: (target, key, receiver) => key === "__v_raw" ? target : unref(Reflect.get(target, key, receiver)),// 当设置代理对象的属性时会触发set: (target, key, value, receiver) => {const oldValue = target[key];if (isRef(oldValue) && !isRef(value)) {// 若旧值是ref响应式数据,且新值不是ref响应式数据,则直接设置旧值的value属性为新值oldValue.value = value;return true;} else {// 若旧值不是ref响应式数据,或新值是ref响应式数据,则直接调用`Reflect.set`设置属性值return Reflect.set(target, key, value, receiver);}}
};
function proxyRefs(objectWithRefs) {return isReactive(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers);
}

proxyRef会先判断传入对象是否是响应式的,若是,则直接返回该对象;否则使用shallowUnwrapHandlers作为处理器创建一个新的Proxy代理对象并返回。

customRef

customRef的本质上就是实例化一个customRefImpl类,并返回该实例。

function customRef(factory) {return new CustomRefImpl(factory);
}class CustomRefImpl {constructor(factory) {__publicField$1(this, "dep");__publicField$1(this, "_get");__publicField$1(this, "_set");__publicField$1(this, "__v_isRef", true);__publicField$1(this, "_value");const dep = this.dep = new Dep();const { get, set } = factory(dep.track.bind(dep), dep.trigger.bind(dep));this._get = get;this._set = set;}get value() {return this._value = this._get();}set value(newVal) {this._set(newVal);}
}

customRefImpl类接受一个工厂函数,该工厂函数接受两个参数函数:tracktrigger,分别用于收集依赖和触发更新,并且工厂函数还会返回一个对象,对象会包含getset的实现,在读取或设置自定义ref实例时,会分别调用getset。该类的实现给了用户更多的自主权去定制响应式行为。

triggerRef

triggerRef的实现如下:

function triggerRef(ref2) {if (ref2.dep) {{ref2.dep.trigger({target: ref2,type: "set",key: "value",newValue: ref2._value});}}
}

triggerRef会调用triggerRefValue触发更新,triggerRefValue后续会介绍。

http://www.dtcms.com/a/344275.html

相关文章:

  • Oracle APEX 经典报表中的Checkbox
  • 期货Level2五档订单簿0.25秒级高频分时及日频历史行情数据使用指南
  • Docker 部署 MySQL 8.0 完整指南:从拉取镜像到配置远程访问
  • 高级SQL优化 | 告别 Hive 中 GROUP BY 的大 KEY 数据倾斜!PawSQL 自适应优化算法详解
  • MsSQL 函数,实现数字转换成人民币大写
  • IDEA基础配置优化指南(中英双版)
  • matlab中随机森林算法的实现
  • AI重塑职业教育:个性化学习计划提效率、VR实操模拟强技能,对接就业新路径
  • 在Excel和WPS表格中如何隐藏单元格的公式
  • 视觉语言对比学习的发展史:从CLIP、BLIP、BLIP2、InstructBLIP(含MiniGPT4的详解)
  • 一分钟了解六通道 CAN(FD) 集线器
  • 第二阶段WinFrom-6:文件对话框,对象的本地保存,序列化与反序列化,CSV文件操作,INI文件读写
  • 【虚拟化】磁盘置备方式的性能损耗对比
  • k8s应用的包管理Helm工具
  • 基于国产麒麟操作系统的Web数据可视化教学解决方案
  • 【Java SE】深入理解继承与多态
  • 使用 YAML 文件,如何优雅地删除 k8s 资源?
  • Apache Druid SSRF漏洞复现(CVE-2025-27888)
  • 孤独伤感视频素材哪里找?分享热门伤感短视频素材资源网站
  • Sklearn 机器学习 房价预估 使用GBDT训练模型
  • 【Linux我做主】细说进程地址空间
  • Ansible入门:自动化运维基础
  • docker 打包
  • 前端项目打包+自动压缩打包文件+自动上传部署远程服务器
  • 设计模式笔记
  • 开题报告被退回?用《基于大数据的慢性肾病数据可视化分析系统》的Hadoop技术,一次通过不是梦
  • Matplotlib 可视化大师系列(五):plt.pie() - 展示组成部分的饼图
  • 故障诊断:基于大模型的实现方法与开源实践(从入门到精通)
  • Matplotlib 可视化大师系列(一):plt.plot() - 绘制折线图的利刃
  • linux----进度条实现和gcc编译