computed
Actually, I think computed is a combination of the effect method and ref, which allows you to pass in an expression and track it as a side effect. Ref constructs a responsive structure through the interceptor of the value attribute. Here is a computed example:
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
Copy the code
Computed returns a ref object that cannot be manually modified by default by taking a getter or a setter&getterOption as a parameter, The main thing this entry function does is parse out the getters and setters in the argument and construct a ComputedRefImpl:
export function computed<T> (getter: ComputedGetter<T>) :ComputedRef<T>
export function computed<T> (options: WritableComputedOptions<T>) :WritableComputedRef<T>
export function computed<T> (getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
if (isFunction(getterOrOptions)) {
getter = getterOrOptions
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
return newComputedRefImpl( getter, setter, isFunction(getterOrOptions) || ! getterOrOptions.set )as any
}
Copy the code
Here are the type definitions for getters and setters:
export type ComputedGetter<T> = (ctx? :any) = > T
export type ComputedSetter<T> = (v: T) = > void
export interface WritableComputedOptions<T> {
get: ComputedGetter<T>
set: ComputedSetter<T>
}
Copy the code
There is nothing to be said for the final return value. Both attributes are found in ComputedRefImpl:
export interface ComputedRef<T = any> extends WritableComputedRef<T> {
readonly value: T
}
export interface WritableComputedRef<T> extends Ref<T> {
readonly effect: ReactiveEffect<T>
}
Copy the code
ComputedRefImpl
ComputedRefImpl is an internal implementation of computed, similar to RefImpl, which caches values through the _value attribute, except that the value is computed by the getter wrapped in effect.
Let’s start with attributes:
_value
: Store calculated values;_dirty
: Completes the calculation of the flag bit. The calculation is completedfalse
, there arereactive
It’s going to change as the value changesfalse
;effect
: Cache execution is delayedeffect
(effect
) wrapped around our incomingsetter
As an executive function;__v_isRef
:ref
Flag bit, indicating that the structure can be deconstructed, andref
Identical characteristics;IS_READONLY
: read only flag bits when we pass only onegetter
Parameter, the property istrue
;
class ComputedRefImpl<T> {
private_value! : Tprivate _dirty = true
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true;
public readonly [ReactiveFlags.IS_READONLY]: boolean
}
Copy the code
Then we look at the setter first, and we just call the setter that was passed in. Shouldn’t we call the trigger function on a set operation to trigger a reference to this computed side effect?
A: This is because setters do not necessarily cause computed values to change, because computed values depend on other responsive objects, and computed results change only when those objects change.
class ComputedRefImpl<T> {
set value(newValue: T) {
this._setter(newValue)
}
}
Copy the code
In fact, computed responsive implementation is quite complex, as shown in the figure below:
step1
Structure:computed state
Internally referencedreactive obj
At this time willgetter
Constructed as a parameter constructeffect
theeffect
[Fixed] Lazy flags are not executed immediatelystep2
: Declares the side effect functioneffect
Internal referencestate.value
thiscomputed ref
;step3
:state.value
triggeredcomputed getter
We know from the following code that the first execution (dirty
) will be calledthis.effect
;this.effect
Pre-executiongetter
Argument, and triggerobj.num
的getter
Now,obj.num track
了this.effect
;this.effect
The result of the calculation is assigned to_value
And return, updatedirty
Mark;- The last
computed state
还track
了step2
performeffect
;
step4
: Execute aset
Operation,set
In the operationobj.num=num
Set offObj. Num setter
Broker andtrigger this.effect
;- Due to the
this.effect
有scheduler
, so it will not be executedgetter
It’s going to implementscheduler
To set updirty
Flag bit, andtrigger computed deps
; computed deps
containsstep2
的effect
, re-executenumpy = state.value
;state.value
Trigger againcomputed getter
, recalculate the value, update the dirty flag bits, the process is complete.
- Due to the
Now we know why triggers don’t happen in setters, because triggers should only happen if computed internal references to reactive change, However, computed setters do not necessarily create setters for referenced Reactive.
And referenced reactive will trigger this.effect if it fires in the setter(whether or not it’s in the setter), and this.effect will trigger automatically.
class ComputedRefImpl<T> {
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean
) {
this.effect = effect(getter, {
lazy: true.scheduler: () = > {
if (!this._dirty) {
this._dirty = true
trigger(toRaw(this), TriggerOpTypes.SET, 'value')}}})this[ReactiveFlags.IS_READONLY] = isReadonly
}
get value() {
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
const self = toRaw(this)
if (self._dirty) {
self._value = this.effect()
self._dirty = false
}
track(self, TrackOpTypes.GET, 'value')
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
}
Copy the code