vue3源码分析 -- ref

案例

通过以下这个案例来进行理解,首先引入refeffect两个函数,之后声明name响应式数据,接着又执行effect函数,该函数传入了一个匿名函数,最后两秒后又修改name

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../../dist/vue.global.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script>
      const { ref, effect } = Vue
      
      const name = ref('jc')
      
      effect(() => {
        document.querySelector('#app').innerHTML = name.value
      })

      setTimeout(() => {
        name.value = 'cc'
      }, 2000)
    </script>
  </body>
</html>

createRef方法

ref函数定义在packages/reactivity/src/ref.ts文件下:

export function ref(value?: unknown) {
  return createRef(value, false)
}

function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

ref函数实际执行的是createRef方法,而该方法实际是返回了一个RefImpl构造函数的实例对象,它会接收传入的值,可能是基本类型也可能是复杂类型,通过 _rawValue 记录原始值,用于之后依赖触发时新旧值的比较

在这里插入图片描述

我们需关注this._value = __v_isShallow ? value : toReactive(value)这行代码,toReactive函数被定义在packages/reactivity/src/reactive.ts中:

export const toReactive = <T extends unknown>(value: T): T =>
  isObject(value) ? reactive(value) : value

可以看出,如果传入的数据为对象类型则调用 reactive 方法,这块逻辑可参考reactive的源码分析;否则就直接返回当前值,此时ref执行完毕,当前name被赋值为jc

在这里插入图片描述

再回过头看下RefImpl构造函数中还有get value()set value()这两个方法,举个例子:

// RefImpl 构造函数
class RefImpl {
    // 实例的 getter 行为: ref.value
    get value() {
      return 'get value'
    }
    // 实例的 setter 行为: ref.value = xxx
    set value(newVal) {
      console.log('set value')
    }
}
const newRef = new RefImpl()
console.log(newRef)

输出结果如下:

在这里插入图片描述

当执行newRef.value时会触发getter,而修改值时会触发setter,这也是为什么我们赋值或者修改 ref 值时需要加上 .value

对于基本类型的数据 ref 是不具备数据监听的,当赋值或修改值时主动触发了 get set 方法, 之后执行effect函数,接着执行赋值行为触发get方法

在这里插入图片描述

get/set

get方法核心trackRefValue(this)实际触发了trackRefValue方法进行数据的依赖收集,该方法定义在packages/reactivity/src/ref.ts文件中:

在这里插入图片描述

实际执行的是triggerEffects,该方法定义在packages/reactivity/src/effect.ts,这块逻辑同reactive,给指定属性绑定对应的fn dep 对象与 ReactiveEffect 相关联,完成整个依赖收集的过程

在这里插入图片描述

之后两秒后进行修改值触发set方法:

在这里插入图片描述

set方法中triggerRefValue(this, newVal)进行依赖触发:

在这里插入图片描述

triggerRefValue方法实际执行triggerEffects,该方法定义在packages/reactivity/src/effect.ts 文件中:

在这里插入图片描述

上面reactive也分析了这块逻辑,最终执行的是每个 effect.run 方法,即传入的匿名函数,从而触发赋值操作,此时整个依赖触发的过程完成

在这里插入图片描述

总结

1) ref 函数本质上做了三件事

  • 返回RefImpl的实例
  • 对数据处理,如果当前数据为基本类型则直接返回;如果是为复杂类型则调用reactive返回reactive数据
  • RefImpl提供get valueset value方法,这就是为什么设置ref值时,需要带上.value

2) ref 基本类型的数据不具备数据监听,赋值或修改值都是主动触发 get set 方法

3)为什么ref类型数据,必须要通过.value访问值呢?

  • 因为ref需要处理基本数据类型的响应性,但是对于基本类型数据而言,它无法通过 proxy 建立代理
  • vue通过get value()set value()定义了两个属性函数,通过主动触发这两个函数(属性调用)的形式来进行依赖收集和依赖触发, 所以我们必须通过.value来保证响应性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值