前言
之前已经了解了ref
、reactive
、effect
、computed
等响应式相关的api
,接下来一起了解一下watch
和watchEffect
watch、watchEffect
watch
、watchEffect
源码较为简单,均通过doWatch
实现的逻辑,看下代码:
// watchEffect
export function watchEffect(
effect: WatchEffect,
options?: WatchOptionsBase
): WatchStopHandle {
// 返回dowatch
return doWatch(effect, null, options)
}
// watch
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.`
)
}
// 返回dowatch
return doWatch(source as any, cb, options)
}
doWatch
doWatch
完成了watch
和watchEffect
的核心逻辑,接下来以抽出来的简易代码来分析整个过程
function doWatch(
source,
cb,
{ immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ
) {
let getter
if(isRef(source)) {
getter = () => source.value
} else if (isReactive(source)) {
getter = () => source
} else if (isFunction(source)) {
if(cb) {
// watch
getter = () => callWithErrorHandling(source)
} else {
// 非watch
getter = () => {
// cleanup存在执行清除副作用函数
cleanup && cleanup()
// 注册onInvalidate
return callWithAsyncErrorHandling(source, [onInvalidate])
}
}
} else {
getter = NOOP
warn(
`Invalid watch source: `,
source,
`A watch source can only be a getter/effect function, a ref, ` +
`a reactive object, or an array of these types.`
)
}
}
初始化阶段,根据入参的不同类型对getter
做了不同的处理,getter
会作为ReactiveEffect
(不记得ReactiveEffect
的话,翻一下之前的effect
文章)的第一个参数传进去,当调用effect.run
时会执行getter
获取新值
callWithErrorHandling
和callWithAsyncErrorHandling
分别是在watch
和非watch
时调用,看一下其各自的实现
简化版:
function callWithErrorHandling(fn, args) {
const res = args ? fn(...args) : fn()
return res
}
function callWithAsyncErrorHandling(fn, args) {
const res = callWithErrorHandling(fn, args)
return res
}
源码:
// callWithErrorHandling
export function callWithErrorHandling(
fn: Function,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
args?: unknown[]
) {
let res
try {
res = args ? fn(...args) : fn()
} catch (err) {
handleError(err, instance, type)
}
return res
}
// callWithAsyncErrorHandling
export function callWithAsyncErrorHandling(
fn: Function | Function[],
instance: ComponentInternalInstance | null,
type: ErrorTypes,
args?: unknown[]
): any[] {
if (isFunction(fn)) {
const res = callWithErrorHandling(fn, instance, type, args)
if (res && isPromise(res)) {
res.catch(err => {
handleError(err, instance, type)
})
}
return res
}
const values = []
for (let i = 0; i < fn.length; i++) {
values.push(callWithAsyncErrorHandling(fn[i], instance, type, args))
}
return values
}
其目的一是为了执行传入的fn
函数,二是对错误进行处理
在非watch
时callWithAsyncErrorHandling
注册了一个onInvalidate
,这个函数就是我们对副作用进行处理时调用的函数,看一下其实现:
let cleanup
let onInvalidate = (fn) => {
// 指定cleanup
// 侦听器被停止时清除副作用
cleanup = effect.onStop = () => callWithErrorHandling(fn)
}
当我们调用onInvalidate
函数时,给cleanup
赋值callWithErrorHandling
,并将onInvalidate
中的参数作为入参传给callWithErrorHandling
,在副作用每次之前前调用effect
即可达到清除副作用的目的
此处给effect
注册一个onStop
,停止监听时触发effect.stop
达到清除副作用的目的
这里刚好解释了官网说的清除副作用调用时机
继续源码的分析,接下来就是当监听的值发生变化时触发cb
即可:
let oldValue = INITIAL_WATCHER_VALUE
const job = () => {
if (!effect.active) {
return
}
// 触发cb
if(cb) {
const newValue = effect.run()
// 副作用即将重新执行时清除副作用
if (cleanup) {
cleanup()
}
callWithAsyncErrorHandling(cb, [
newValue,
oldValue,
onInvalidate
])
oldValue = newValue
} else {
effect.run()
}
}
// 执行job
let scheduler = () => job()
// 传入scheduler
const effect = new ReactiveEffect(getter, scheduler)
这里将之前的getter
和scheduler
传入ReactiveEffect
,每次调用effect.run
时会触发getter
获取新值,当监听的值发生变化时会触发scheduler
,从而触发job -> callWithAsyncErrorHandling -> cb
(cb存在即watch,cb不存在时初始化阶段已经注册了onInvalidate
),最终返回可以中介监听的函数:
return () => {
// 停止监听,清除副作用
effect.stop()
}
基本到这里就结束了,可以参考简易代码后再去看源码,方便理解
结语
感觉还是需要总结一下语言,但又或者对源码理解不够