Vue3 Ref大揭秘:你所不知道的真相

前言

vue3使用Proxy实现了响应式,这件众所周知的事,会让人下意识的认为reactive、ref等响应式API都使用了Proxy进行了代理。读了源码才发现,ref并没有使用Proxy实现响应式,而是使用了Class的getter和setter进行了数据拦截

1. Ref

源码文件位置:packages\reactivity\src\ref.ts

ref函数很简单,就是直接调用createRef

js 复制代码
export function ref(value?: unknown) {
  return createRef(value);
}

2. createRef

createRef:1. 先使用isRef检查传入的值是否是已经被代理过的,是的话直接返回;2. 如果是没有被代理过,则实例化RefImpl类

js 复制代码
function createRef(rawValue: unknown, shallow = false) {
    if (isRef(rawValue)) {
      // 已经被代理过,直接返回值
      return rawValue
    }
    //实例化RefImpl
    return new RefImpl(rawValue, shallow)
  }

isRef:判断目标对象的__v_isRef属性值是否为true

js 复制代码
export function isRef(r: any): r is Ref {
    return Boolean(r && r.__v_isRef === true)
  }

isRef只是判断逻辑,接着看此次的重点RefImpl类

3. RefImpl

1. 私有属性

_value:访问属性时返回的值

__v_isRef:标识当前对象以被Ref处理过

_rawValue:传入的原始值

_shallow:是否是ref的浅层作用形式

2. 构造函数

构造函数中先判断_shallow是否为true,为true直接返回传入的原始值,否则调用convert函数

js 复制代码
constructor(private _rawValue: T, private readonly _shallow = false) {
    this._value = _shallow ? _rawValue : convert(_rawValue)
  }

3. get、set

在get方法中调用track收集依赖;在set方法中当新值与旧值不等时,调用trigger触发更新。track与trigger不清楚的,可参考:更适合入门的vue3响应式原理解析(纯干货分享)

js 复制代码
class RefImpl<T> {
    //访问属性时返回的值  
    private _value: T
  
    //标识当前对象以被Ref处理过
    public readonly __v_isRef = true
    
    //_rawValue:传入的原始值
    //_shallow:是否是ref的浅层作用形式
    constructor(private _rawValue: T, private 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的定义与reactive不同。reactive是通过Proxy进行数据拦截,而ref则是通过类的getter和setter进行数据拦截

也解决了另一个疑惑点,为什么ref需要.value取值。因为取值函数get、存值函数set的定义的属性名称为value。

4. convert

convert:判断传入的值是否为对象,如果是对象交给reactive处理,否则返回原值

js 复制代码
const convert = <T extends unknown>(val: T): T =>
  isObject(val) ? reactive(val) : val

说明ref也是可以处理引用数据类型的,因为内部会进行判断,如果是对象,则使用reactive处理。

ref可以定义原始数据类型,也可以定义引用数据类型,那么reactive可以定义原始数据类型吗?答案是不可以,因为Proxy只能代理对象

js 复制代码
//Uncaught TypeError: Cannot create proxy with a non-object as target or handler
const proxy = new Proxy(1, {
    get: function (target, key) {
      return target[key];
    },
  });

四、总结

  1. reactive通过Proxy进行数据拦截;ref通过class的Getter/Setter进行数据拦截

  2. reactive只能定义引用数据类型;ref可以定义原始数据类型,也能定义引用数据类型

因为ref取值需要通过.value,会多一层嵌套,因此在开发中,常使用reactive定义引用数据类型,使用ref定义原始数据类型

相关推荐
晓晓莺歌1 分钟前
vue3某一个路由切换,导致所有路由页面均变成空白页
前端·vue.js
Up九五小庞30 分钟前
开源埋点分析平台 ClkLog 本地部署 + Web JS 埋点测试实战--九五小庞
前端·javascript·开源
qq_177767371 小时前
React Native鸿蒙跨平台数据使用监控应用技术,通过setInterval每5秒更新一次数据使用情况和套餐使用情况,模拟了真实应用中的数据监控场景
开发语言·前端·javascript·react native·react.js·ecmascript·harmonyos
烬头88211 小时前
React Native鸿蒙跨平台应用实现了onCategoryPress等核心函数,用于处理用户交互和状态更新,通过计算已支出和剩余预算
前端·javascript·react native·react.js·ecmascript·交互·harmonyos
Jing_jing_X3 小时前
CPU 架构:x86、x64、ARM 到底是什么?为什么程序不能通用?
arm开发·架构·cpu
天人合一peng4 小时前
Unity中button 和toggle监听事件函数有无参数
前端·unity·游戏引擎
方也_arkling5 小时前
别名路径联想提示。@/统一文件路径的配置
前端·javascript
毕设源码-朱学姐5 小时前
【开题答辩全过程】以 基于web教师继续教育系统的设计与实现为例,包含答辩的问题和答案
前端
qq_177767375 小时前
React Native鸿蒙跨平台自定义复选框组件,通过样式数组实现选中/未选中状态的样式切换,使用链式调用替代样式数组,实现状态驱动的样式变化
javascript·react native·react.js·架构·ecmascript·harmonyos·媒体
web打印社区5 小时前
web-print-pdf:突破浏览器限制,实现专业级Web静默打印
前端·javascript·vue.js·electron·html