function watchEffect(effect, options) {
return doWatch(effect, null, options);//⭐️⭐️返回一个watch函数
}
/**
*⭐️⭐️⭐️⭐️⭐️doWatch
* @param {*} source effect函数
* @param {*} { immediate, deep, flush, onTrack, onTrigger } 监听函数option配置
*/
function doWatch(source, cb, { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ, instance = currentInstance) {
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.`);
};
let getter;
let forceTrigger = false;
if (isRef(source)) {
getter = () => source.value;
forceTrigger = !!source._shallow;
}
else if (isReactive(source)) {
getter = () => source;
deep = true;
}
else if (isArray(source)) {
getter = () => source.map(s => {
if (isRef(s)) {
return s.value;
}
else if (isReactive(s)) {
return traverse(s);
}
else if (isFunction(s)) {
return callWithErrorHandling(s, instance, 2 /* WATCH_GETTER */);
}
else {
warnInvalidSource(s);
}
});
}
else if (isFunction(source)) {
if (cb) {
// getter with cb
getter = () => callWithErrorHandling(source, instance, 2 /* WATCH_GETTER */);
}
else {
// no cb -> simple effect
getter = () => {
if (instance && instance.isUnmounted) {
return;
}
if (cleanup) {
cleanup();
}
return callWithErrorHandling(source, instance, 3 /* WATCH_CALLBACK */, [onInvalidate]);
};
}
}
else {
getter = NOOP;
warnInvalidSource(source);
}
if (cb && deep) {
const baseGetter = getter;
getter = () => traverse(baseGetter());
}
let cleanup;
const onInvalidate = (fn) => {
cleanup = runner.options.onStop = () => {
callWithErrorHandling(fn, instance, 4 /* WATCH_CLEANUP */);
};
};
let oldValue = isArray(source) ? [] : INITIAL_WATCHER_VALUE;
const job = () => {
if (!runner.active) {
return;
}
if (cb) {
// watch(source, cb)
const newValue = runner();
if (deep || forceTrigger || hasChanged(newValue, oldValue)) {
// cleanup before running cb again
if (cleanup) {
cleanup();
}
callWithAsyncErrorHandling(cb, instance, 3 /* WATCH_CALLBACK */, [
newValue,
// pass undefined as the old value when it's changed for the first time
oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
onInvalidate
]);
oldValue = newValue;
}
}
else {
// watchEffect
runner();
}
};
// 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;
}
else if (flush === 'post') {
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense);
}
else {
// default: 'pre'
scheduler = () => {
if (!instance || instance.isMounted) {
queuePreFlushCb(job);
}
else {
// with 'pre' option, the first call must happen before
// the component is mounted so it is called synchronously.
job();
}
};
}
const runner = effect(getter, {
lazy: true,
onTrack,
onTrigger,
scheduler
});
recordInstanceBoundEffect(runner, instance);
// initial run
if (cb) {
if (immediate) {
job();
}
else {
oldValue = runner();
}
}
else if (flush === 'post') {
queuePostRenderEffect(runner, instance && instance.suspense);
}
else {
runner();
}
return () => {
stop(runner);
if (instance) {
remove(instance.effects, runner);
}
};
}
/**
*⭐️⭐️⭐️⭐️⭐️effect
* @param {*} fn
* @param {*} options option配置
*/
function effect(fn, options = EMPTY_OBJ) {
if (isEffect(fn)) {
fn = fn.raw;
}
const effect = createReactiveEffect(fn, options);
if (!options.lazy) {
effect();
}
return effect;
}
/**
*⭐️⭐️⭐️⭐️⭐️createReactiveEffect
* @param {*} fn
* @param {*} options option配置
*/
function createReactiveEffect(fn, options) {
const effect = function reactiveEffect() {
if (!effect.active) {
return options.scheduler ? undefined : fn();
}
if (!effectStack.includes(effect)) {
cleanup(effect);
try {
enableTracking();
effectStack.push(effect);
activeEffect = effect;
return fn();
}
finally {
effectStack.pop();
resetTracking();
activeEffect = effectStack[effectStack.length - 1];
}
}
};
effect.id = uid++;
effect.allowRecurse = !!options.allowRecurse;
effect._isEffect = true;
effect.active = true;
effect.raw = fn;
effect.deps = [];
effect.options = options;
return effect;
}