watchEffect
作用: 用来追踪响应式依赖,并在追踪的时候自动触发一次,后序检测到响应式依赖的话,会再次更新,注意所有的里面的响应式数据(ref\reactive) 都会自动被加入依赖中
用法: 传入一个副作用方法,并会自动追踪里面的响应式依赖,另外还可以传入一个可选的options,用于控制副作用触发的时机:pre|post|sync,分别是组件渲染前后和同步,默认是pre
watch
作用: 用来检测指定data源的变化;
用法: 用于检测的对象可以是一个ref,也可以是一个getter方法,也可以是一个数组,然后传入一个回调,参数可以获取新旧值,另外可以配置options来设置{immediate,deep}
源码时间 ⏰
因为两者共用发放,所以一起看,
// watchEffect
export function watchEffect(
effect: WatchEffect,
options?: WatchOptionsBase
): WatchStopHandle {
return doWatch(effect, null, options)
}
// watch 利用函数重载来实现类型优化
// 我们可以清楚的看到watch的三种传参方式:
// ref,
// 一个reactive对象(如果想监听某个属性通过getter函数),
// 监听多个data的数组方式
export function watch<
T extends Readonly<Array<WatchSource<unknown> | object>>,
Immediate extends Readonly<boolean> = false
>(
sources: T,
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
options?: WatchOptions<Immediate>
): WatchStopHandle
// overload #2: single source + cb
export function watch<T, Immediate extends Readonly<boolean> = false>(
source: WatchSource<T>,
cb: WatchCallback<T, Immediate extends true ? (T | undefined) : T>,
options?: WatchOptions<Immediate>
): WatchStopHandle
// overload #3: watching reactive object w/ cb
export function watch<
T extends object,
Immediate extends Readonly<boolean> = false
>(
source: T,
cb: WatchCallback<T, Immediate extends true ? (T | undefined) : T>,
options?: WatchOptions<Immediate>
): WatchStopHandle
// implementation
export function watch<T = any, Immediate extends Readonly<boolean> = false>(
source: T | WatchSource<T>,
cb: any,
options?: WatchOptions<Immediate>
): WatchStopHandle {
if (__DEV__ && !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 as any, cb, options)
}
可以看到watchEffect和watch事实上都是返回了doWatch这个方法,下面就带着问题一起探索一下这个方法
watchEffect 的实现
- watchEffect如何自动收集依赖的?
首先看一下我们给doWatch传入的参数:
doWatch(effect, null, options)
然后我们大概看一下doWatch的格式
function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null,
{ immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ,
instance = currentInstance
): WatchStopHandle {// ... }
可以看到总共接受4个参数,一个被检测源,一个回调函数,一个配置项,一个实例项且默认当前实例
返回值是WatchStopHandle类型,其实就是一个没有返回值的函数,即:
type WatchStopHandle = ()=>void
然后我们就对号入座,首先我们给出一个例子
const count = ref(0)
watchEffect(()=>console.log(count.value))
所以对应依次是:
- source :
()=>console.log(count.value), - cb: null
- options: null
- instance default
然后进入方法内部去查看,首先是一个条件判断
if (__DEV__ && !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.`
)
}
}
这里我们就清楚了cb是用来区别是watch还是watchEffect,如果没有传入回调函数,那就是watchEffect,并且同时我们也不能指定immediate,deep这两个配置项,所以正好对应了官网文档说的:
watch 与 watchEffect 在手动停止,副作用无效 (将 onInvalidate 作为第三个参数传递给回调),flush timing 和 debugging 有共享行为。即(flush, onTrack, onTrigger)
然后会有进一步的判断:主要是区别source的类型,是ref怎么处理,是reactive对象怎么处理,是数组怎么处理,我们目前只关注watchEffect,所以只看传入函数这一项,结合注释看一下
let getter: () => any
else if (isFunction(source)) {
if (cb) { //如果有回调函数 那就是watch了
// watch ....
} else {
// no cb -> simple effect
getter = () => { // 这里就是watchEffect的判断
if (instance && instance.isUnmounted) {
return
}
if (cleanup) {
cleanup()
}
return callWithErrorHandling(
source,
instance,
ErrorCodes.WATCH_CALLBACK,
[onInvalidate]
)
}
}
}
首先我们查看当前实例有没有挂在到dom上,cleanup是清除副作用的,通过接受onInvalidate参数来传入的回调函数,这主要用来 在执行异步请求这种异步任务时,在执行完毕以前依赖的状态发生变化时处理,主要在re-run的时候或者组件卸载的时候执行
然后我们看一下是如何收集依赖的:
watch
首先看一下watch可以传入的参数:
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.`
)