computed,watchEffect,watch

86 阅读5分钟

1. watch

  • Vue官网,watch的地址
  • watch侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。
  • watch()默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。

可选参数

  • immediate:是否立即执行,watch默认懒侦听,默认immediate:false,如果把该值改为true,就会在setup函数执行阶段就调用回调函数一次,且第一次调用时旧值是undefined
  • deep:深度侦听,表现为当一个有多层次响应式对象,内部属性发生变化时是否侦听得到,触发回调函数。
  • flush:调整回调函数的刷新时机。默认情况下,侦听回调的触发会在Vue组件更新之前
  1. pre:默认值,侦听器回调会在父组件更新 (如有) 之后所属组件的 DOM 更新之前被调用
  2. post:将侦听回调触发时机改为Vue组件更新之后
  3. sync:会在 Vue 进行任何更新之前触发
  4. flush刷新机制官网地址
  • onTrack / onTrigger
  1. onTrack:将在响应属性或引用作为依赖项目被跟踪时被调用。
  2. onTrigger:将侦听器回调被依赖项目的变更触发时被调用。
  3. onTrack/onTrigger官网地址
const plusOne = computed(() => count.value + 1, {
  onTrack(e) {
    // 当 count.value 被追踪为依赖时触发
    debugger
  },
  onTrigger(e) {
    // 当 count.value 被更改时触发
    debugger
  }
})

// 访问 plusOne,会触发 onTrack
console.log(plusOne.value)

// 更改 count.value,应该会触发 onTrigger
count.value++
  • once:回调函数只会运行一次,侦听器在回调函数首次运行后就自动停止了。

2. watchEffect()

  • vue中watchEffect的官网地址
  • 立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
  • 参数只有flushonTrackonTrigger
  • 自带了immediate:true

数据如何进行监听?

  • 先判断isRef,是的话,取data.value getter = () => source.value;
  • 不是,再判断isReactive,是的话,取 getter = () => source;
  • 不是,再判断isArray,是的话,再用map对里面的值进行一个一个数据判断
        isMultiSource = true;
        forceTrigger = source.some(s => reactivity.isReactive(s) || reactivity.isShallow(s));
        getter = () => source.map(s => {
            if (reactivity.isRef(s)) {
                return s.value;
            }
            else if (reactivity.isReactive(s)) {
                return traverse(s);
            }
            else if (shared.isFunction(s)) {
                return callWithErrorHandling(s, instance, 2 /* ErrorCodes.WATCH_GETTER */);
            }
            else {
                warnInvalidSource(s);
            }
        });
  • 不是,是isFunction,判断第二个参数是否存在
        if (cb) {
            // getter with cb
            getter = () => callWithErrorHandling(source, instance, 2 /* ErrorCodes.WATCH_GETTER */);
        }
        else {
            // no cb -> simple effect
            getter = () => {
                if (instance && instance.isUnmounted) {
                    return;
                }
                if (cleanup) {
                    cleanup();
                }
                return callWithAsyncErrorHandling(source, instance, 3 /* ErrorCodes.WATCH_CALLBACK */, [onCleanup]);
            };
        }
  • 还不是, getter = shared.NOOP;返回了一个空函数。

3. computed

  • Vue中computed官网
  • 接受一个getter函数,返回一个只读的响应式ref对象。该ref通过.value暴露getter函数的返回值。它也可以接受一个带有getset函数的对象来创建一个可写的ref对象。
  • getter函数中,在追踪的响应式依赖没有发生变化时,返回都会是上次缓存的数据。
  • 只有在computed中的响应式依赖发生变化时,才会再次进行运算。

4.computed,watchEffect,watch的区别

  • computed只接受一个getter函数,watch和watchEffect可以接受多个参数。
  • computed必须要有一个返回值(ref,只读),watch和watchEffect的返回会是一个用于停止侦听的函数。
  • computed、watchEffect都会自动追踪响应式依赖,watch需要主动追踪响应式依赖。
  • computed着重计算,watch和watchEffect着重侦听
  • computed不能做异步请求或者更改DOM,watch和watchEffect则相反
  • computed有缓存机,watch和watchEffect没有
  • computed和watchEffect在第一次时会执行,watch是需要在有变化时才执行
class ComputedRefImpl {
    constructor(getter, _setter, isReadonly, isSSR) {
        this._setter = _setter;
        this.dep = undefined;
        this.__v_isRef = true;
        this[_a$1] = false;
        this._dirty = true;
        this.effect = new ReactiveEffect(getter, () => {
            if (!this._dirty) {
                this._dirty = true;
                triggerRefValue(this);
            }
        });
        this.effect.computed = this;
        this.effect.active = this._cacheable = !isSSR;
        this["__v_isReadonly" /* ReactiveFlags.IS_READONLY */] = isReadonly;
    }
    get value() {
        // the computed ref may get wrapped by other proxies e.g. readonly() #3376
        const self = toRaw(this);
        trackRefValue(self);
        if (self._dirty || !self._cacheable) {
            self._dirty = false;
            self._value = self.effect.run();
        }
        return self._value;
    }
    set value(newValue) {
        this._setter(newValue);
    }
}

Vue源代码 watch和watchEffect:

// Simple effect.
function watchEffect(effect, options) {
    return doWatch(effect, null, options);
}
function watchPostEffect(effect, options) {
    return doWatch(effect, null, { ...options, flush: 'post' } );
}
function watchSyncEffect(effect, options) {
    return doWatch(effect, null, { ...options, flush: 'sync' } );
}
// initial value for watchers to trigger on undefined initial values
const INITIAL_WATCHER_VALUE = {};
// implementation
function watch(source, cb, options) {
    if (!shared.isFunction(cb)) {
        warn(`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
            `Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
            `supports \`watch(source, cb, options?) signature.`);
    }
    return doWatch(source, cb, options);
}
function doWatch(source, cb, { immediate, deep, flush, onTrack, onTrigger } = shared.EMPTY_OBJ) {
    if (!cb) {
        if (immediate !== undefined) {
            warn(`watch() "immediate" option is only respected when using the ` +
                `watch(source, callback, options?) signature.`);
        }
        if (deep !== undefined) {
            warn(`watch() "deep" option is only respected when using the ` +
                `watch(source, callback, options?) signature.`);
        }
    }
    const warnInvalidSource = (s) => {
        warn(`Invalid watch source: `, s, `A watch source can only be a getter/effect function, a ref, ` +
            `a reactive object, or an array of these types.`);
    };
    const instance = reactivity.getCurrentScope() === (currentInstance === null || currentInstance === void 0 ? void 0 : currentInstance.scope) ? currentInstance : null;
    // const instance = currentInstance
    let getter;
    let forceTrigger = false;
    let isMultiSource = false;
    if (reactivity.isRef(source)) {
        getter = () => source.value;
        forceTrigger = reactivity.isShallow(source);
    }
    else if (reactivity.isReactive(source)) {
        getter = () => source;
        deep = true;
    }
    else if (shared.isArray(source)) {
        isMultiSource = true;
        forceTrigger = source.some(s => reactivity.isReactive(s) || reactivity.isShallow(s));
        getter = () => source.map(s => {
            if (reactivity.isRef(s)) {
                return s.value;
            }
            else if (reactivity.isReactive(s)) {
                return traverse(s);
            }
            else if (shared.isFunction(s)) {
                return callWithErrorHandling(s, instance, 2 /* ErrorCodes.WATCH_GETTER */);
            }
            else {
                warnInvalidSource(s);
            }
        });
    }
    else if (shared.isFunction(source)) {
        if (cb) {
            // getter with cb
            getter = () => callWithErrorHandling(source, instance, 2 /* ErrorCodes.WATCH_GETTER */);
        }
        else {
            // no cb -> simple effect
            getter = () => {
                if (instance && instance.isUnmounted) {
                    return;
                }
                if (cleanup) {
                    cleanup();
                }
                return callWithAsyncErrorHandling(source, instance, 3 /* ErrorCodes.WATCH_CALLBACK */, [onCleanup]);
            };
        }
    }
    else {
        getter = shared.NOOP;
        warnInvalidSource(source);
    }
    if (cb && deep) {
        const baseGetter = getter;
        getter = () => traverse(baseGetter());
    }
    let cleanup;
    let onCleanup = (fn) => {
        cleanup = effect.onStop = () => {
            callWithErrorHandling(fn, instance, 4 /* ErrorCodes.WATCH_CLEANUP */);
        };
    };
    // in SSR there is no need to setup an actual effect, and it should be noop
    // unless it's eager or sync flush
    let ssrCleanup;
    if (isInSSRComponentSetup) {
        // we will also not call the invalidate callback (+ runner is not set up)
        onCleanup = shared.NOOP;
        if (!cb) {
            getter();
        }
        else if (immediate) {
            callWithAsyncErrorHandling(cb, instance, 3 /* ErrorCodes.WATCH_CALLBACK */, [
                getter(),
                isMultiSource ? [] : undefined,
                onCleanup
            ]);
        }
        if (flush === 'sync') {
            const ctx = useSSRContext();
            ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = []);
        }
        else {
            return shared.NOOP;
        }
    }
    let oldValue = isMultiSource
        ? new Array(source.length).fill(INITIAL_WATCHER_VALUE)
        : INITIAL_WATCHER_VALUE;
    const job = () => {
        if (!effect.active) {
            return;
        }
        if (cb) {
            // watch(source, cb)
            const newValue = effect.run();
            if (deep ||
                forceTrigger ||
                (isMultiSource
                    ? newValue.some((v, i) => shared.hasChanged(v, oldValue[i]))
                    : shared.hasChanged(newValue, oldValue)) ||
                (false  )) {
                // cleanup before running cb again
                if (cleanup) {
                    cleanup();
                }
                callWithAsyncErrorHandling(cb, instance, 3 /* ErrorCodes.WATCH_CALLBACK */, [
                    newValue,
                    // pass undefined as the old value when it's changed for the first time
                    oldValue === INITIAL_WATCHER_VALUE
                        ? undefined
                        : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
                            ? []
                            : oldValue,
                    onCleanup
                ]);
                oldValue = newValue;
            }
        }
        else {
            // watchEffect
            effect.run();
        }
    };
    // important: mark the job as a watcher callback so that scheduler knows
    // it is allowed to self-trigger (#1727)
    job.allowRecurse = !!cb;
    let scheduler;
    if (flush === 'sync') {
        scheduler = job; // the scheduler function gets called directly
    }
    else if (flush === 'post') {
        scheduler = () => queuePostRenderEffect(job, instance && instance.suspense);
    }
    else {
        // default: 'pre'
        job.pre = true;
        if (instance)
            job.id = instance.uid;
        scheduler = () => queueJob(job);
    }
    const effect = new reactivity.ReactiveEffect(getter, scheduler);
    {
        effect.onTrack = onTrack;
        effect.onTrigger = onTrigger;
    }
    // initial run
    if (cb) {
        if (immediate) {
            job();
        }
        else {
            oldValue = effect.run();
        }
    }
    else if (flush === 'post') {
        queuePostRenderEffect(effect.run.bind(effect), instance && instance.suspense);
    }
    else {
        effect.run();
    }
    const unwatch = () => {
        effect.stop();
        if (instance && instance.scope) {
            shared.remove(instance.scope.effects, effect);
        }
    };
    if (ssrCleanup)
        ssrCleanup.push(unwatch);
    return unwatch;
}