import { effect, ReactiveEffect, effectStack } from './effect' import { Ref, UnwrapRef } from './ref' import { isFunction, NOOP } from '@vue/shared' export interface ComputedRef extends WritableComputedRef { readonly value: UnwrapRef } export interface WritableComputedRef extends Ref { readonly effect: ReactiveEffect } export type ComputedGetter = () => T export type ComputedSetter = (v: T) => void export interface WritableComputedOptions { get: ComputedGetter set: ComputedSetter } export function computed(getter: ComputedGetter): ComputedRef export function computed( options: WritableComputedOptions ): WritableComputedRef export function computed( getterOrOptions: ComputedGetter | WritableComputedOptions ): any { const isReadonly = isFunction(getterOrOptions) const getter = isReadonly ? (getterOrOptions as ComputedGetter) : (getterOrOptions as WritableComputedOptions).get const setter = isReadonly ? __DEV__ ? () => { console.warn('Write operation failed: computed value is readonly') } : NOOP : (getterOrOptions as WritableComputedOptions).set let dirty = true let value: T const runner = effect(getter, { lazy: true, // mark effect as computed so that it gets priority during trigger computed: true, scheduler: () => { dirty = true } }) return { _isRef: true, // expose effect so computed can be stopped effect: runner, get value() { if (dirty) { value = runner() dirty = false } // When computed effects are accessed in a parent effect, the parent // should track all the dependencies the computed property has tracked. // This should also apply for chained computed properties. trackChildRun(runner) return value }, set value(newValue: T) { setter(newValue) } } } function trackChildRun(childRunner: ReactiveEffect) { if (effectStack.length === 0) { return } const parentRunner = effectStack[effectStack.length - 1] for (let i = 0; i < childRunner.deps.length; i++) { const dep = childRunner.deps[i] if (!dep.has(parentRunner)) { dep.add(parentRunner) parentRunner.deps.push(dep) } } }