vue3-watchEffect源码解读
目录
vue3 watchEffect源码解读
1. watchEffect
函数入口
watchEffect
函数定义在
@vue/runtime-core
包中,以下是简化后的核心代码:
import { effect, ReactiveEffect } from '@vue/reactivity'
export function watchEffect(
effectFn: (onInvalidate: InvalidateCbRegistrator) => void,
options?: WatchEffectOptions
): StopHandle {
return doWatch(effectFn, null, options)
}
watchEffect
接收一个副作用函数effectFn
和可选的配置项options
。- 它实际上调用了
doWatch
函数,将effectFn
作为第一个参数传入,第二个参数为null
,这与watch
的区别在于,watch
通常会传入要监听的数据源,而watchEffect
会自动收集副作用函数中使用的响应式数据作为依赖。
2. doWatch
函数核心逻辑
function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null,
{ immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): StopHandle {
// ...
let getter: () => any
let forceTrigger = false
let isMultiSource = false
if (isFunction(source)) {
if (cb) {
// watch(source, cb)
getter = () => source()
} else {
// watchEffect(source)
getter = () => {
if (cleanup) {
cleanup()
}
return callWithErrorHandling(source, instance, ErrorCodes.WATCH_CALLBACK, [
onInvalidate
])
}
}
}
// ...
let cleanup: () => void
let onInvalidate: InvalidateCbRegistrator = (fn: () => void) => {
cleanup = effect.onStop = () => {
callWithAsyncErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
}
}
const effect = new ReactiveEffect(getter, () => {
if (!instance || instance.isUnmounted) {
return
}
if (cb) {
// watch(source, cb)
// ...
} else {
// watchEffect
schedulePostFlushCb(run)
}
})
effect.onTrack = onTrack
effect.onTrigger = onTrigger
if (immediate) {
if (cb) {
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
getter(),
undefined,
onInvalidate
])
} else {
run()
}
} else {
run()
}
return () => {
effect.stop()
if (instance && instance.scope) {
remove(instance.scope.effects!, effect)
}
}
}
关键步骤分析:
- getter 函数的创建
:
- 当
cb
为null
时,说明是watchEffect
调用。getter
函数会先检查是否有清理函数cleanup
,如果有则执行它,然后调用传入的副作用函数source
,并将onInvalidate
作为参数传入。
- 当
onInvalidate
函数 :- 用于注册清理函数。当副作用函数重新执行或停止时,清理函数会被调用。例如,在副作用函数中发起异步请求,当请求还未完成时副作用函数就需要重新执行,这时可以使用
onInvalidate
来取消之前的请求。
- 用于注册清理函数。当副作用函数重新执行或停止时,清理函数会被调用。例如,在副作用函数中发起异步请求,当请求还未完成时副作用函数就需要重新执行,这时可以使用
ReactiveEffect
实例的创建 :ReactiveEffect
是 Vue 响应式系统中用于管理副作用函数和依赖关系的核心类。它接收getter
函数和一个调度函数作为参数。- 调度函数会在依赖项发生变化时被调用。对于
watchEffect
,它会将run
函数调度到微任务队列中执行,确保在 DOM 更新后再执行副作用函数。
- 立即执行
:
- 如果
immediate
为true
,则会立即执行副作用函数。对于watchEffect
,直接调用run
函数。
- 如果
- 返回停止函数
:
doWatch
函数返回一个停止函数,调用该函数可以停止watchEffect
的监听,即调用effect.stop()
方法停止ReactiveEffect
实例的运行。
3. ReactiveEffect
类的作用
export class ReactiveEffect<T = any> {
active = true
deps: Dep[] = []
// ...
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null
) {}
run() {
if (!this.active) {
return this.fn()
}
try {
activeEffect = this
// 清理上一次的依赖
cleanupEffect(this)
// 开始收集依赖
trackOpBit = 1 << ++effectTrackDepth
return this.fn()
} finally {
// 恢复之前的依赖收集状态
trackOpBit = 1 << --effectTrackDepth
activeEffect = undefined
}
}
stop() {
if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
ReactiveEffect
类的run
函数会在执行副作用函数之前,将当前的ReactiveEffect
实例设置为全局的activeEffect
,以便在副作用函数内部访问响应式数据时进行依赖收集。- 在执行完副作用函数后,会恢复之前的依赖收集状态。
stop
函数用于停止ReactiveEffect
实例的运行,清理依赖关系,并执行可能存在的停止回调函数onStop
。
总结
watchEffect
的核心原理是通过
ReactiveEffect
类来管理副作用函数和依赖关系。在副作用函数执行过程中,自动收集所使用的响应式数据作为依赖。当这些依赖发生变化时,调度函数会被触发,重新执行副作用函数。同时,
onInvalidate
提供了清理机制,用于处理副作用函数重新执行或停止时的清理工作。