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
提供了清理机制,用于处理副作用函数重新执行或停止时的清理工作。