Vue3 源码阅读(6)- computed
计算属性大家肯定都很熟悉了
其内部实现依赖 effect
,追踪到变化后重新计算结果,并且返回的是一个类似 ref
的玩意
其源码在 packages/reactivity/src/computed.ts
computed
我们直接定位到方法,可以看到有好几种声明:
1 | // 只传 getter |
分别表示只传 getter
和 getter
,setter
都传的情况
来看实现:
定义
getter
和setter
:1
2let getter: ComputedGetter<T>;
let setter: ComputedSetter<T>;分别处理只传了
getter
和都传的情况:1
2
3
4
5
6
7
8
9
10
11
12const onlyGetter = isFunction(getterOrOptions);
if (onlyGetter) {
getter = getterOrOptions;
setter = __DEV__
? () => {
console.warn('Write operation failed: computed value is readonly');
}
: NOOP;
} else {
getter = getterOrOptions.get;
setter = getterOrOptions.set;
}给
getter
和setter
赋值构建一个
ComputedRefImpl
的实例并返回:1
2
3
4
5
6
7
8
9
10
11
12
13const cRef = new ComputedRefImpl(
getter,
setter,
onlyGetter || !setter,
isSSR
);
if (__DEV__ && debugOptions && !isSSR) {
cRef.effect.onTrack = debugOptions.onTrack;
cRef.effect.onTrigger = debugOptions.onTrigger;
}
return cRef as any;
ComputedRefImpl
是计算属性的核心实现
成员变量
dep
: 依赖当前计算属性的副作用集合_value
: 计算的结果effect
: 用于捕获getter
方法中依赖的响应式值的副作用__v_isRef
: 标识该对象为ref
[ReactiveFlags.IS_READONLY]
: 标识该对象是否为只读的_dirty
: 用于标识当前计算结果是否已经不可用(依赖的响应式值变动了,但是还没计算)_cacheable
: 不为 SSR 环境时为true
,我们只讨论非 SSR 的实现
构造器
接收四个参数:
getter
: 计算方法_setter
: 赋值方法isReadonly
: 是否只读,computed
有setter
的情况下才为true
isSSR
: 是否为服务端渲染,只讨论false
的情况
1 | export class ComputedRefImpl<T> { |
来看实现
首先将
getter
方法作为回调构建一个副作用1
2
3
4
5
6this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true;
triggerRefValue(this);
}
});同时传入了一个适配器方法,
getter
中的响应式对象属性变化并调用trigger
时,此时会优先调用适配器(第二个参数传的函数)适配器逻辑很简单
如果当前计算结果标记为干净的
则将其设为脏
并触发依赖当前计算结果(
computed.value
)的副作用
设置各种 Flag
标记当前副作用为
computed
类型1
this.effect.computed = this;
标记当前副作用为活跃的,当前计算属性是可缓存的(只讨论
isSSR
为false
的情况)1
this.effect.active = this._cacheable = !isSSR;
设置只读标记
1
this[ReactiveFlags.IS_READONLY] = isReadonly;
value
getter
用于拦截
value
属性的读取行为1
2
3
4
5
6
7export class ComputedRefImpl<T> {
...
get value() {
...
}
...
}来详细看下
get value() { ... }
的实现可能被其它方法套娃(
readonly
等),首先确保拿到的对象本身1
2// the computed ref may get wrapped by other proxies e.g. readonly() #3376
const self = toRaw(this);追踪各种副作用和依赖
先调用
trackRefValue
追踪依赖value
的副作用1
trackRefValue(self);
接着如果当前值为脏,则调用副作用的
run
来重新运行getter
计算结果,将结果赋值给_value
1
2
3
4if (self._dirty || !self._cacheable) {
self._dirty = false;
self._value = self.effect.run()!;
}最后返回计算结果
_value
1
return self._value;
setter
就很简单,直接调用当前
_setter
1
2
3
4
5
6export class ComputedRefImpl<T> {
...
set value(newValue: T) {
this._setter(newValue);
}
}
计算流程
例如下面代码:
1 | const countA = reactive({ count: 1 }); |
调用
computed
构建计算属性对象c
声明副作用
e
e
的回调会立刻执行,回调读取了计算属性的值c.value
计算属性立刻计算出值并返回,此时响应式系统也得知副作用
e
依赖c.value
回调执行完毕
countA.count++
修改了响应式对象的值,触发了计算属性c
内部的副作用c.effect
c.effect
触发后会立刻触发了依赖c.value
的副作用,副作用e
被触发,执行3~5
步