1. watch
- Vue官网,watch的地址
watch侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。watch()默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。
可选参数
immediate:是否立即执行,watch默认懒侦听,默认immediate:false,如果把该值改为true,就会在setup函数执行阶段就调用回调函数一次,且第一次调用时旧值是undefined。deep:深度侦听,表现为当一个有多层次响应式对象,内部属性发生变化时是否侦听得到,触发回调函数。flush:调整回调函数的刷新时机。默认情况下,侦听回调的触发会在Vue组件更新之前
pre:默认值,侦听器回调会在父组件更新 (如有) 之后、所属组件的 DOM 更新之前被调用post:将侦听回调触发时机改为Vue组件更新之后sync:会在 Vue 进行任何更新之前触发- flush刷新机制官网地址
onTrack / onTrigger:
onTrack:将在响应属性或引用作为依赖项目被跟踪时被调用。onTrigger:将侦听器回调被依赖项目的变更触发时被调用。- 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的官网地址
- 立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
- 参数只有
flush,onTrack,onTrigger - 自带了
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函数的返回值。它也可以接受一个带有get和set函数的对象来创建一个可写的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;
}