案例
通过以下这个案例来进行理解,首先引入ref和effect两个函数,之后声明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 value和set value方法,这就是为什么设置ref值时,需要带上.value
2) ref 基本类型的数据不具备数据监听,赋值或修改值都是主动触发 get 和 set 方法
3)为什么ref类型数据,必须要通过.value访问值呢?
- 因为
ref需要处理基本数据类型的响应性,但是对于基本类型数据而言,它无法通过proxy建立代理 - 而
vue通过get value()和set value()定义了两个属性函数,通过主动触发这两个函数(属性调用)的形式来进行依赖收集和依赖触发, 所以我们必须通过.value来保证响应性
1849

被折叠的 条评论
为什么被折叠?



