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 arereactiveIt’s going to change as the value changesfalse;
  • effect: Cache execution is delayedeffect(effect) wrapped around our incomingsetterAs an executive function;
  • __v_isRef:refFlag bit, indicating that the structure can be deconstructed, andrefIdentical characteristics;
  • IS_READONLY: read only flag bits when we pass only onegetterParameter, 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:

  1. step1Structure:computed stateInternally referencedreactive objAt this time willgetterConstructed as a parameter constructeffecttheeffect[Fixed] Lazy flags are not executed immediately
  2. step2: Declares the side effect functioneffectInternal referencestate.valuethiscomputed ref;
  3. step3:state.valuetriggeredcomputed getterWe know from the following code that the first execution (dirty) will be calledthis.effect;
    1. this.effectPre-executiongetterArgument, and triggerobj.numgetterNow,obj.num trackthis.effect;
    2. this.effectThe result of the calculation is assigned to_valueAnd return, updatedirtyMark;
    3. The lastcomputed statetrackstep2performeffect;
  4. step4: Execute asetOperation,setIn the operationobj.num=numSet offObj. Num setterBroker andtrigger this.effect;
    1. Due to thethis.effectscheduler, so it will not be executedgetterIt’s going to implementschedulerTo set updirtyFlag bit, andtrigger computed deps;
    2. computed depscontainsstep2effect, re-executenumpy = state.value;
    3. state.valueTrigger againcomputed getter, recalculate the value, update the dirty flag bits, the process is complete.

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