持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情
前言
上一节学了如何使用watch 方法,和一部分源码,这一篇我们再继续深入研究watch的源码。
doWatch
doWatch 函数的代码比较长,我先看其中一部分
const instance = currentInstance
let getter: () => any
let forceTrigger = false
let isMultiSource = false
if (isRef(source)) {
getter = () => source.value
forceTrigger = isShallow(source)
} else if (isReactive(source)) {
getter = () => source
deep = true
} else if (isArray(source)) {
isMultiSource = true
forceTrigger = source.some(isReactive)
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, ErrorCodes.WATCH_GETTER)
}
})
} else if (isFunction(source)) {
if (cb) {
// getter with cb
getter = () =>
callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
} else {
// no cb -> simple effect
getter = () => {
if (instance && instance.isUnmounted) {
return
}
if (cleanup) {
cleanup()
}
return callWithAsyncErrorHandling(
source,
instance,
ErrorCodes.WATCH_CALLBACK,
[onCleanup]
)
}
}
} else {
getter = NOOP
__DEV__ && warnInvalidSource(source)
}
首先创建了三个变量,getter最终会传给副作用函数,所以getter就是数据更新后触发的方法,forceTrigger 标识是否需要强制更新,isMultiSource 标记传入的是单个数据源还是以数组形式传入的多个数据源。
接下来就是根据不同的情况创建 getter 方法
- 先判断 source 是否为 ref,是的话 getter 就是一个返回 source.value 值的函数。
- 再判断 source 是否为 isReactive,是的化 getter 直接返回 source。
- 如果 source 是数组,说明是多个数据源,置 isMultiSource 为true,并在 getter 里面遍历 source,分别判断里面的值类型,如果是方法就直接调用。
- 如果 source 是方法,且watch有传回调,在getter 里面就直接调用这个 source,如果没有传回调,那么说明当前用户使用的 api 不是 watch,而是 watchEffect,如果组件实例已经卸载,则不执行,直接返回,否则执行 cleanup 清除依赖,最后执行 source 函数
- 如果上面的情况都不满足,说明没有 getter 函数,并且开发环境中会报警告。
处理完 getter,接下来会判断 deep 属性是否为 true,如果为 true,将使用 traverse 来包裹 getter 函数,对数据源中的每个属性递归遍历进行监听。
if (cb && deep) {
const baseGetter = getter
getter = () => traverse(baseGetter())
}
接下来会定义一个 job 方法,这个函数最终会作为调度器中的回调函数传入
let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
const job: SchedulerJob = () => {
if (!effect.active) {
return
}
if (cb) {
// watch(source, cb)
const newValue = effect.run()
if (
deep ||
forceTrigger ||
(isMultiSource
? (newValue as any[]).some((v, i) =>
hasChanged(v, (oldValue as any[])[i])
)
: hasChanged(newValue, oldValue))
) {
if (cleanup) {
cleanup()
}
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
newValue,
oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
onCleanup
])
oldValue = newValue
}
} else {
// watchEffect
effect.run()
}
}
- 在这个调度方法中,会判断回调函数是否有传,没有传就说明是 watchEffect,直接运行 effect.run。
- 如果有回调,会先执行run方法,的到新值 newValue,如果 deep 为 true,或者在 source 里面的旧值 oldValue 和 newValue 有变化,那么就调用回调方法,并把新值和旧值都传过去,这就是为什么我们在回调方法中,能获得旧值的逻辑。
定义好调度方法后,就开始创建调度器和副作用函数。
let scheduler: EffectScheduler
if (flush === 'sync') {
scheduler = job as any // the scheduler function gets called directly
} else if (flush === 'post') {
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
// default: 'pre'
scheduler = () => queuePreFlushCb(job)
}
const effect = new ReactiveEffect(getter, scheduler)
根据 flush 值的不同,调度器也有所不同。
最后会进行初始化的回调方法执行
// initial run
if (cb) {
if (immediate) {
job()
} else {
oldValue = effect.run()
}
} else if (flush === 'post') {
queuePostRenderEffect(
effect.run.bind(effect),
instance && instance.suspense
)
} else {
effect.run()
}
return () => {
effect.stop()
if (instance && instance.scope) {
remove(instance.scope.effects!, effect)
}
}
如果有传参数 immediate 并为true,那么一开始就执行一次 job 方法。最后返回一个方法,这个方法里面调用 effect的stop()方法。
总结
这节我们详细解释了 watch 方法的源码,其实还有个 watchEffect 的api使用的也是 doWatch 方法,并再里面进行了独立处理,但是大体逻辑是一样的。