我们先看源码实现, 为了方便,我去掉了一些开发环境和SSR情况下的逻辑分支判断的代码。
代码位置:\core\packages\reactivity\src\computed.ts
class ComputedRefImpl {
public dep: any;
public effect: ReactiveEffect;
private _dirty: boolean;
private _value
constructor(getter) {
this._dirty = true;
this.dep = createDep();
this.effect = new ReactiveEffect(getter, () => {
// scheduler
// 只要触发了这个函数说明响应式对象的值发生改变了
// 那么就解锁,后续在调用 get 的时候就会重新执行,所以会得到最新的值
if (this._dirty) return;
// 解锁
this._dirty = true;
// 触发依赖
triggerRefValue(this);
});
}
get value() {
// 收集依赖
trackRefValue(this);
// 锁上,只可以调用一次
// 当数据改变的时候才会解锁
// 这里就是缓存实现的核心
// 解锁动作是在上面的 scheduler 里面做的
if (this._dirty) {
this._dirty = false;
// 这里执行 run 的话,其实就是执行用户传入的 fn
this._value = this.effect.run();
}
return this._value;
}
}
function computed(getterOrOptions) {
let getter;
let setter;
if (isFunction(getterOrOptions)) {
getter = getterOrOptions;
setter = () => {
console.warn('Write operation failed: computed value is readonly');
};
}
else {
getter = getterOrOptions.get;
setter = getterOrOptions.set;
}
return new ComputedRefImpl(getter, setter, isFunction(getterOrOptions) || !getterOrOptions.set);
}
一般使用场景,举个栗子: 1、接受一个getter函数,并为getter返回的值返回一个只读的响应式ref对象。 2、它还可以使用具有get和set函数的对象来创建可写的ref对象。
// 通过以下方式创建出来的是一个只读的 ref
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 如果修改plusOne的值会报提示:Write operation failed: computed value is readonly
// 想要创建一个可读可写的ref对象,通过以下的方式:
// 可以使用具有get和set函数的对象来创建可写的ref对象。
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
Q&A
有人可能会疑惑为什么要增加_dirty这个变量? 这是因为要做缓存的缘故,保证getter函数里面的响应对象的值发生改变了才会再次触发fn,如果值没有改变,则读取缓存里面的值,提高性能。
总结:
1、解析源码中computed方法的实现方式,实现逻辑比较简单,理解了_dirty基本上就理解了computed的缓存核心 2、例举了两种场景,如果传入的getter是个函数的话,computed返回的就是一个只读的响应式ref对象,如果传入的getter中有set和get函数,则可以创建一个可写的ref对象 3、解释了_dirty变量的作用