首先引入 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>
</head>
<body>
<div id="app"></div>
<script type="module">
import {
effect,
ref
} from "./reactivity.js";
const count = ref(1);
effect(() => {
document.querySelector("#app").innerHTML = count.value;
});
setTimeout(() => {
count.value++;
}, 1000);
</script>
</body>
</html>
ref 实现
export function ref(value?: unknown) {
return createRef(value, false)
}
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
// 创建RefTmpl类
return new RefImpl(rawValue, shallow)
}
ref 函数实际执行的是 createRef 方法,而该方法实际是返回了一个 RefImpl 构造函数的实例对象:
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(
value: T,
public readonly __v_isShallow: boolean,
) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
const useDirectValue =
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
newVal = useDirectValue ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
const oldVal = this._rawValue
this._rawValue = newVal
this._value = useDirectValue ? newVal : toReactive(newVal)
triggerRefValue(this, DirtyLevels.Dirty, newVal, oldVal)
}
}
}
RefImpl 构造函数会接收传入的值,可能是基本类型也可能是复杂类型,通过 _rawValue 记录原始值,用于之后依赖触发时新旧值的比较,我们需关注 this._value = __v_isShallow ? value : toReactive(value),如果已经是响应式对象,无需再次赋值
1、依赖收集
export function trackRefValue(ref: RefBase<any>) {
if (shouldTrack && activeEffect) {
ref = toRaw(ref)
trackEffect(
activeEffect,
(ref.dep ??= createDep(
() => (ref.dep = undefined),
ref instanceof ComputedRefImpl ? ref : undefined,
)),
void 0,
) // 逻辑同reactive
}
}
这块逻辑同 reactive,给指定属性绑定对应的 fn,目的是 dep 对象与 ReactiveEffect 相关联,完成整个依赖收集的过程。
2、派发更新
export function triggerRefValue(
ref: RefBase<any>,
dirtyLevel: DirtyLevels = DirtyLevels.Dirty,
newVal?: any,
oldVal?: any,
) {
ref = toRaw(ref)
const dep = ref.dep
if (dep) {
triggerEffects(dep, dirtyLevel, void 0)
}
}
上篇 reactive 我们也分析了这块逻辑,最终执行的是每个 effect.run 方法,即传入的匿名函数,从而触发赋值操作,此时整个依赖触发的过程完成
总结
- ref 函数本质上做了三件事:一是返回 RefImpl 的实例;二是对数据处理,如果当前数据为基本类型,则直接返回;如果为复杂类型,则调用 reactive 返回 reactive 数据;三是 RefImpl 提供 get value 和 set value 方法,这就是为什么设置 ref 值时,需要带上 .value。
- ref 基本类型的数据不具备数据监听,赋值或修改值都是主动触发 get 和 set 方法。
- 为什么 ref 类型数据,必须要通过 .value 访问值呢?
a. 因为 ref 需要处理基本数据类型的响应性,但是对于基本类型数据而言,它无法通过 proxy 建立代理。
b. 而 vue 通过 get value() 和 set value() 定义了两个属性函数,通过主动触发这两个函数(属性调用)的形式来进行依赖收集和依赖触发。
c. 所以我们必须通过 .value 来保证响应性。