大家好,我是剑大瑞。
这边文章次要剖析Vue3中watch API
的应用 & 实现原理,心愿文中的内容对你有所帮忙。
如果有谬误之处还望斧正。我会非常感谢你的。
如果没有对Vue3的响应式原理不相熟,倡议先浏览第一篇Vue3 reactivity 源码解析。
computed
API
在上篇文章中咱们剖析了computed
的原理。在runtime core
中,有对computed
做了一层解决,次要是记录以后实例的computed Effect
至instance.effects
。不便组件卸载的时候,清空依赖。
runtime core
中的代码较为简单,咱们一起简略看下。
- 记录组件创立阶段的
computed Effect
至instance.effects
,不便组件卸载的时候,移除以后实例的computed effect
- 咱们应用的
computed
API就是通过runtime core
解决过的computed
import { computed as _computed,} from '@vue/reactivity'// 来自component.ts文件function recordInstanceBoundEffect(effect,instance = currentInstance) { if (instance) { ;(instance.effects || (instance.effects = [])).push(effect) }}function computed(getterOrOptions) { const c = _computed(getterOrOptions) // c.effect recordInstanceBoundEffect(c.effect) return c}
watch
API
Vue3中新增了一个响应式函数:watchEffect
。在剖析watch & watchEffect
之前,咱们一起先回顾下这两个API如何应用。
回顾
watch
与watchEffect
都属于Vue中的响应式API。
留神:一提到响应式,大家就应该想到:getter & track
、setter & trigger
。
watchEffect
watchEffect
能够依据响应数据状态的变动,主动或者从新执行传入的副作用函数。- 他承受一个回调函数,并在创立的时候立刻执行,同时对齐进行响应式依赖追踪。
- 即建设以后传入的回调函数与所有相干
effect
的依赖关系。 - 并在依赖变动的时候从新运行该回调函数。
- 并会返回一个
stop
函数,用来进行侦听,即断开以后watchEffect
与其所有依赖的effect
之间的关系
const count = ref(0)const stop = watchEffect(() => console.log(count.value))// -> logs 0setTimeout(() => { count.value++ // -> logs 1}, 100)// 进行侦听stop()count.value += 1
当然watchEffect
也能够承受异步回调函数作为参数。当回调函数为异步时:
watchEffect
能够给传入的函数传递一个异步的onInvalidate
函数作为入参,用来注册清理watchEffect
生效时的回调函数何时
watchEffect
会生效:- 当手动调用
stop
函数的时候 - 当组件卸载的时候
- 当手动调用
const stop = watchEffect(onInvalidate => { const token = performAsyncOperation(id.value) onInvalidate(() => { // 当调用stop函数时,会执行给onInvalidate传入的回调函数 token.cancel() })})onUnmounted(() => { console.log('组件卸载')})
为了进步刷新效率,Vue的响应式零碎会缓存并异步解决所有watchEffect
副作用函数,以防止同一个“tick
” 中多个状态扭转导致的不必要的反复调用。
对于如何缓存并异步解决,稍后源码中进行解析
配置watchEffect
,watchEffect
能够承受两个参数,第二个参数对watchEffect
进行配置:
- 默认状况下
(flush: 'pre')
,watchEffec
t副作用会在所有的组件update
前执行 - 当设置
flush: 'post'
时,组件更新后会从新运行watchEffect
副作用 - 当设置
flush: 'sync'
时,这将强制成果始终同步触发watchEffect
副作用
<template> <div>{{ count }}</div></template><script>export default { setup() { const count = ref(0) // 更新前触发 watchEffect(() => { console.log(count.value) }, { flush: 'pre' }) // 更新后触发 watchEffect(() => { console.log(count.value) }, { flush: 'post' }) // 同步触发 watchEffect(() => { console.log(count.value) }, { flush: 'sync' }) return { count } }}</script>
watch
watch
等同于组件侦听器property
- 须要侦听特定的响应式数据源
- 并在回调喊胡世宗执行副作用
- 默认状况下是惰性的,只有当侦听的数据源发生变化的时候才会执行回调
侦听单个数据源:
// 侦听一个 getterconst state = reactive({ count: 0 })watch( () => state.count, (count, prevCount) => { /* ... */ })// 间接侦听refconst count = ref(0)watch(count, (count, prevCount) => { /* ... */})
侦听多个数据源(间接侦听ref
):
留神尽管侦听的是多个数据源,然而当多个数据源产生扭转的时候,侦听器仍只会执行一次
setup() { const firstName = ref('') const lastName = ref('') watch([firstName, lastName], (newValues, prevValues) => { console.log(newValues, prevValues) }) const changeValues = () => { firstName.value = 'John' lastName.value = 'Smith' // 打印 ["John", "Smith"] ["", ""] } return { changeValues }}
侦听响应式对象
deep
可进行深度侦听immediate
可进行立刻侦听
const state = reactive({ id: 1, attributes: { name: '', }})watch( () => state, (state, prevState) => { console.log('not deep', state.attributes.name, prevState.attributes.name) })// 深度并立刻响应侦听watch( () => state, (state, prevState) => { console.log('deep', state.attributes.name, prevState.attributes.name) }, { deep: true, immediate: true })state.attributes.name = 'Alex' // 日志: "deep" "Alex" "Alex"
这里须要说下 【副作用】、【依赖】都是咱们上一篇文章中提到的
effet
。比拟要害的是,咱们这里接触的是Vue源码中的第二个级别的
effect
,第一个是compute Effect
。这次要说的是watch Effect
。
Ok,到这里咱们根本曾经回顾完这两个响应式API如何应用了,上面咱们联合源码,进行剖析。
剖析
通过回顾局部的内容,根本曾经对watch & watchEffect
有了点感觉,既然要剖析源码实现,最好是带着问题来看:
watchEffect
是如何进行侦听的?watchEffect
是如何进行函数缓存的?watchEffect
是如何异步进行刷新的?watch
是如何侦听单个或者多个数据源的?watch
是如何进行深度或者立刻侦听响应的?
Vue3中的watch
代码中设计的性能比拟多,为了不便了解,咱们拆开来一点一点进行解析
watchEffect
是如何进行侦听的?
后面提到watch
其实也是一个effect
,所谓的侦听就是watch与其余effect
之间建设一个依赖关系,当数据发生变化的时候,去遍历执行所有的effect
,就会执行watch
。
在上一篇文章中咱们提到,effect
中有个stop
函数,用于断开传入effect
与之相干的依赖之间的关系。
所谓的进行侦听就是断开watch
与所有相干effect
的依赖关系。
当创立watch Effect
时,会为其保护一个deps
属性,用于存储所有的dep
。故当咱们创立watch
的时候,将以后runner
传给stop
函数,并返回一个函数,用户调用的时候,就会进行侦听。
上面代码咱们临时省略与进行侦听无关的代码,只需了解能解答问题的局部即可。
// reactive effect.ts 文件export function stop(effect) { if (effect.active) { cleanup(effect) if (effect.options.onStop) { effect.options.onStop() } effect.active = false }}function cleanup(effect) { const { deps } = effect if (deps.length) { for (let i = 0; i < deps.length; i++) { deps[i].delete(effect) } deps.length = 0 }}// 真正的watch函数function doWatch( source, cb, { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ, instance ) { // 定义runner // watch 级别的effect // runner执行,即执行getter函数 const runner = effect(getter, { lazy: true, onTrack, onTrigger, scheduler }) // 返回一个stop函数 // 用于断开runner与其余依赖之间的关系 // 并将其将从instance.effects中移除 return () => { stop(runner) // if (instance) { remove(instance.effects!, runner) } }}
runner
就是effect API
创立的watch Effect
。watch
对数据源进行侦听的时候,就会与其余依赖之间建设关系。即effect
与dep
之间互相耦合的关系。
当须要进行侦听的时候,通过调用doWatch
返回的函数就能够断开runner
与其余dep
的依赖关系。
watch
是如何侦听单个或者多个数据源的?
在回顾局部咱们晓得,watch
能够进行多种数据响应式数据类型的监听。
当watch
侦听的数据源发生变化的时候就会执行callback
。这就是后面咱们说的响应式。
在应用watch
时,doWatch
会创立一个 getter
函数,用于确定数据源与callback
之间的关系。
getter
函数用于获取数据源的更新后的值。当getter
函数执行的时候,就会触发依赖收集。
所以Vue3是在getter
函数中对数据源进行判断侦听的。上面咱们先看下源码的getter
局部,在持续剖析。
function doWatch( source, cb, { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ, instance = currentInstance) { /** * 省略局部代码... */ let getter let forceTrigger = false let isMultiSource = false /* Start: 开始定义getter函数 */ if (isRef(source)) { // 第一种状况: // 源是ref类型 getter = () => source.value forceTrigger = !!source._shallow } 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 { // 已上都不是 则进行警示 __DEV__ && warnInvalidSource(s) } }) } 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, [onInvalidate] ) } } } else { // 数据源可能有问题 进行校验 提醒 getter = NOOP __DEV__ && warnInvalidSource(source) } /* End: 定义getter函数完结 */ /** * 省略局部代码... */ }
从下面的代码咱们能够看出,doWatch
外部共对数据源进行了四种状况的判断。
- 并且当数据源是响应式数据类型时,会主动进行深度侦听
- 当侦听的是多个数据源的时候,会进行递归遍历
- 当数据源是函数的时候,
getter
函数最终会调用callWithAsyncErrorHandling
异步执行侦听的函数。 - 当
deep
为ttrue
时,会对getter
进行递归遍历。
再联合下面剖析进行侦听的代码,能够晓得,当runner
函数执行的时候,就是执行getter
获取数据源新值的时候。
深度侦听函数:
- 是一个深度递归遍历函数
- 通过seen函数判断循环援用的状况
- 最终返回的是原始类型数据
// 递归遍历获取值,seen用于避免陷入死循环function traverse(value, seen = new Set()) { if ( !isObject(value) || seen.has(value) || (value)[ReactiveFlags.SKIP] ) { return value } seen.add(value) if (isRef(value)) { traverse(value.value, seen) } else if (isArray(value)) { for (let i = 0; i < value.length; i++) { traverse(value[i], seen) } } else if (isSet(value) || isMap(value)) { value.forEach((v: any) => { traverse(v, seen) }) } else if (isPlainObject(value)) { for (const key in value) { traverse((value)[key], seen) } } return value}
watchEffect是如何进行函数缓存 & 异步进行刷新的?
咱们晓得当应用effect
函数创立runner
的时候,其实是创立了一个watch Effect
。
这里回顾下,在computed
中,Vue3是间接将effect
返回的函数,赋给ComputedRefImpl
实例的effect
属性,并在实例的getter
函数中调用this.effect,从而获取 value。
在应用watch
的时候,咱们并没有间接或间接应用effect
函数返回的watch Effect函数获取新值。也没有必要这么应用。
然而咱们须要在相干依赖发生变化的时候从新执行watch Effect
获取新值 & 执行callback
。那该如何做?
答案是通过给watch Effect
配置scheduler
属性。
当进行响应派发的时候,会触发trigger
函数,trigger
函数最终会遍历执行所有相干effect
。
在执行effect
的过程中会判断effect.scheduler
是否存在,如果存在就会执行scheduler
函数。
而watch Effect
就是在scheduler
中做的副作用函数的缓存和异步刷新的。
还是原来的套路,让咱们先看下scheduler
源码局部:
// 真正的watch函数function doWatch( source, cb, { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ, instance = currentInstance) { /** * 省略局部代码... */ // 调度器,有没有想到computed API 创立的时候,在配置项中设置的 scheduler // 在computed中scheduler次要负责重置 dirty // 当 watche Effect 侦测的数据源发生变化的时候 // 会进行trigger,遍历执行所有与数据源相干的 effect // 在遍历的过程中会判断effect.scheduler 是否存在 // 如果存在 则会执行scheduler(任务调度器),这一点与咱们第一篇提到的computed的原理一样 // scheduler执行 其实就是在执行job,job执行就是在执行 runner Effect // 即 watch Effect let scheduler if (flush === 'sync') { // 同步更新 scheduler = job // 任务调度函数被间接调用 } else if (flush === 'post') { // 组件更新后 scheduler = () => queuePostRenderEffect(job, instance && instance.suspense) } else { // 默认状况下 // 通过queuePreFlushCb缓存job,并异步更新副作用函数 scheduler = () => { if (!instance || instance.isMounted) { queuePreFlushCb(job) } else { // 应用 'pre' 选项,第一次调用必须在组件装置之前产生,以便同步调用。 job() } } } // 创立watch Effect const runner = effect(getter, { lazy: true, onTrack, onTrigger, scheduler }) /** * 省略局部代码... */ }
通过下面的代码,咱们简略剖析scheduler
的创立过程:
- 次要与创立
watch
时配置的flush无关 - 在默认下状况下
scheduler
外部通过queuePreFlushCb
将job缓存在待执行队列中,并通过Promise.resolve
异步更新队列从而防止不必要的反复调用 - 通过
Promise
创立微工作。在update
之前执行所有的副作用函数,等于是进步了副作用函数的优先级
这里咱们先晓得
watchEffect
是通过queuePreFlushCb
做到的副作用函数缓存 & 异步批量更新。在后续的文章中会剖析scheduler.ts
局部的内容。到时候就会明确其作用。另:基础薄弱的同学,倡议相熟下浏览器的宏工作与微工作相干常识。
在下面的代码中,能够晓得scheduler
次要的职责就是依据状况对job
进行解决,那job
是什么?
job
就是异步队列中的一个个工作。次要负责:
- 通过判断
callback
,对watch
与watchEffect
进行判断 - 通过执行
runner
获取新值 - 通过
callWithAsyncErrorHandling
对callback函数进行异步解决,并将新旧值传给callback
,这也是咱们为什么能够在watch
中拿到侦听数据源,变动前后value的起因。
上面一起看下job
局部的代码实现:
// Simple effect.// watchEffect 的创立形式function watchEffect( effect, options) { return doWatch(effect, null, options)}// watch 的创立形式function watch (source, cb, options) { 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, cb, options)}// 真正的watch函数function doWatch( source, cb, { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ, instance = currentInstance) { /** * 省略局部代码... */ let cleanup // 定义生效时须要传参的函数 let onInvalidate = (fn) => { // 用于执行用户传进来的fn函数 cleanup = runner.options.onStop = () => { callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP) } } let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE // 定义工作队列中的工作 // 用于执行runner函数 // 执行的过程会进行track & trigger const job = () => { if (!runner.active) { return } if (cb) { // watch(source, cb) // runner执行就是在执行getter函数,获取newValue const newValue = runner() if ( deep || forceTrigger || (isMultiSource ? (newValue).some((v, i) => hasChanged(v, (oldValue)[i]) ) : hasChanged(newValue, oldValue)) || (__COMPAT__ && isArray(newValue) && isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)) ) { if (cleanup) { cleanup() } // 执行回调函数 // 因为咱们在传入的cb中很有可能读取或者更改响应式数据 // 因而可能会进行 track || trigger // 将newValue & oldValue传给cb callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [ newValue, oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue, onInvalidate ]) // 将新值赋值给旧值 oldValue = newValue } } else { // watchEffect(effect) // watchEffect // watchEffect API的解决形式,间接执行runner runner() } } // 将job标记为一个能够侦测的回调函数,以便调度器晓得他能够主动进行响应触发(trigger) job.allowRecurse = !!cb /** * 省略局部代码... */ }
通过下面代码,能够晓得:
- 对于
watchEffect
,执行job
,就是在间接执行runner
函数 - 对于
watch
,首先须要通过runner
获取新的value
,并将新旧值传给callback
函数。
watch是如何进行深度或者立刻侦听响应的?
这里就很简略了,间接上代码:
深度侦听就是去遍历递归原来的getter
函数
立刻侦听即间接执行job
函数,触发runner
,并执行callback
。
// 真正的watch函数function doWatch( source, cb, { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ, instance = currentInstance) { /** * 省略局部代码... */ // 深度侦听 if (cb && deep) { // 深度侦听,则递归遍历getter函数返回的值 const baseGetter = getter getter = () => traverse(baseGetter()) } /** * 省略局部代码... */ // initial run if (cb) { // 立刻响应侦听 if (immediate) { // 立刻执行 // 即进行track & trigger job() } else { oldValue = runner() } } else if (flush === 'post') { queuePostRenderEffect(runner, instance && instance.suspense) } else { runner() } /** * 省略局部代码... */ }
如何做Vue2的兼容解决
Vue3在doWatch
函数中,还做了一层Vue2的兼容解决,次要是通过对getter
函数进行了一层重载,并对getter
函数返回的value
进行了深度递归遍历。
// 真正的watch函数function doWatch( source, cb, { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ, instance = currentInstance) { /** * 省略局部代码... */ // 2.x array mutation watch compat // Vue2做兼容解决 if (__COMPAT__ && cb && !deep) { const baseGetter = getter getter = () => { const val = baseGetter() if ( isArray(val) && checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance) ) { traverse(val) } return val } } /** * 省略局部代码... */ }
总结
- 通过下面的剖析,咱们当初把握了两个源码级别的
Effect
,一个是computed Effect
,一个是watch Effect
。 watch
能对数据源进行响应式侦听。次要是通过将数据源转化为getter
函数。- 并通过
effect
建设watch Effect
与相干依赖之间的关系。 - 当数据源发生变化的时候,会触发
Trigger
,进行响应派发,遍历执行所有相干的effect
。 - 当
effect.scheduler
存在时,就会执行scheduler
函数,而watch
外部通过scheduler
,对job
工作进行了缓存,并放在一个待执行队列中,在update
前,会通过promise
异步执行job
工作。 job
执行,就会获取数据源变动后的值,并将新旧value
传给用户创立watch
时的回调函数。实现侦听工作。
不要遗记,在job中获取新值也会触发Track
工作。
最初让咱们看下残缺的watch
相干局部的代码:
// watcheffect.export function watchEffect( effect: WatchEffect, options?: WatchOptionsBase): WatchStopHandle { return doWatch(effect, null, options)}// 进行重载,侦听多个数据源 & cbexport function watch< T extends MultiWatchSources, Immediate extends Readonly<boolean> = false>( sources: [...T], cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>, options?: WatchOptions<Immediate>): WatchStopHandle// 重载:侦听多个数据源,并且数据源是只读的export function watch< T extends Readonly<MultiWatchSources>, Immediate extends Readonly<boolean> = false>( source: T, cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>, options?: WatchOptions<Immediate>): WatchStopHandle// 重载:简略watch Effect & cbexport function watch<T, Immediate extends Readonly<boolean> = false>( source: WatchSource<T>, cb: WatchCallback<T, Immediate extends true ? (T | undefined) : T>, options?: WatchOptions<Immediate>): WatchStopHandle// 重载:侦听响应式对象 & cbexport 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// 执行创立 watchexport function watch<T = any, Immediate extends Readonly<boolean> = false>( source: T | WatchSource<T>, cb: any, options?: WatchOptions<Immediate>): WatchStopHandle { if (__DEV__ && !isFunction(cb)) { // 省略... } // 返回的是一个stop函数 return doWatch(source as any, cb, options)}// 真正的watch函数function doWatch( source: WatchSource | WatchSource[] | WatchEffect | object, cb: WatchCallback | null, { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ, instance = currentInstance): WatchStopHandle { // dev环境下判断 immediate, deep 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.` ) } } // 校验数据源 const warnInvalidSource = (s: unknown) => { 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.` ) } let getter: () => any let forceTrigger = false let isMultiSource = false /* Start: 开始定义getter函数 */ if (isRef(source)) { // 源是ref类型 getter = () => source.value forceTrigger = !!source._shallow } 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 { // 已上都不是 则进行警示 __DEV__ && warnInvalidSource(s) } }) } else if (isFunction(source)) { // 数据源是函数 if (cb) { // getter with cb getter = () => callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER) } else { // no cb -> simple effect // 没有传回调函数的状况,watchEffect getter = () => { if (instance && instance.isUnmounted) { return } if (cleanup) { cleanup() } return callWithAsyncErrorHandling( source, instance, ErrorCodes.WATCH_CALLBACK, [onInvalidate] ) } } } else { getter = NOOP __DEV__ && warnInvalidSource(source) } /* End: 定义getter函数完结 */ // Vue2做兼容解决 if (__COMPAT__ && cb && !deep) { const baseGetter = getter getter = () => { const val = baseGetter() if ( isArray(val) && checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance) ) { traverse(val) } return val } } if (cb && deep) { // 深度侦听,则递归遍历getter函数返回的值 const baseGetter = getter getter = () => traverse(baseGetter()) } let cleanup: () => void // 定义生效时须要传参的函数 let onInvalidate: InvalidateCbRegistrator = (fn: () => void) => { cleanup = runner.options.onStop = () => { callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP) } } // 服务端渲染的状况下,不用创立一个真正的effect, onInvalidate 应该为一个空对象, // 触发 immediate 为true if (__NODE_JS__ && isInSSRComponentSetup) { // we will also not call the invalidate callback (+ runner is not set up) onInvalidate = NOOP if (!cb) { getter() } else if (immediate) { callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [ getter(), undefined, onInvalidate ]) } return NOOP } let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE // 定义工作队列中的工作,用于执行runner函数,执行的过程会进行track & trigger const job: SchedulerJob = () => { if (!runner.active) { return } if (cb) { // watch(source, cb) // runner执行就是在执行getter函数,获取newValue const newValue = runner() if ( deep || forceTrigger || (isMultiSource ? (newValue as any[]).some((v, i) => hasChanged(v, (oldValue as any[])[i]) ) : hasChanged(newValue, oldValue)) || (__COMPAT__ && isArray(newValue) && isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)) ) { // watch API的解决形式 // cleanup before running cb again if (cleanup) { cleanup() } // 执行回调函数 // 因为咱们在传入的cb中很有可能读取或者更改响应式数据 // 因而可能会进行 track || trigger // 将newValue & oldValue传给cb callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [ newValue, oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue, onInvalidate ]) // 将新值赋值给旧值 oldValue = newValue } } else { // watchEffect // watchEffect API的解决形式,间接执行runner runner() } } // 将job标记为一个能够侦测的回调函数,以便调度器晓得他能够主动进行响应触发(trigger) job.allowRecurse = !!cb // // 调度器,有没有想到computed API 创立的时候,在配置项中设置的 scheduler // 在computed中scheduler次要负责重置 dirty // 当 watche Effect 侦测的数据源发生变化的时候 // 会进行trigger,遍历执行所有与数据源相干的 effect // 在遍历的过程中会判断effect.scheduler 是否存在 // 如果存在 则会执行scheduler(任务调度器),这一点与咱们第一篇提到的computed的原理一样 // scheduler执行 其实就是在执行job,job执行就是在执行 runner Effect // 即watch Effect let scheduler: ReactiveEffectOptions['scheduler'] if (flush === 'sync') { // 同步更新 scheduler = job as any // 任务调度函数被间接调用 } else if (flush === 'post') { // 组件更新后 scheduler = () => queuePostRenderEffect(job, instance && instance.suspense) } else { // default: 'pre' // 默认状况下 scheduler = () => { if (!instance || instance.isMounted) { queuePreFlushCb(job) } else { // 应用 'pre' 选项,第一次调用必须在组件装置之前产生,以便同步调用。 job() } } } // 定义runner, watch 级别的effect,runner执行,即执行getter函数 const runner = effect(getter, { lazy: true, onTrack, onTrigger, scheduler }) // 将watch effect 存至instance.effects,当组件卸载的时候会清空以后runner与依赖之间的关系 recordInstanceBoundEffect(runner, instance) // initial run if (cb) { if (immediate) { // 立刻执行,即进行track & trigger job() } else { oldValue = runner() } } else if (flush === 'post') { queuePostRenderEffect(runner, instance && instance.suspense) } else { runner() } // 返回一个stop函数,用于断开runner与其余依赖之间的关系,并将其将从instance.effects中移除 return () => { stop(runner) // if (instance) { remove(instance.effects!, runner) } }}// this.$watch,组件实例上的watch APIexport function instanceWatch( this: ComponentInternalInstance, source: string | Function, value: WatchCallback | ObjectWatchOptionItem, options?: WatchOptions): WatchStopHandle { const publicThis = this.proxy as any // 定义getter函数 const getter = isString(source) ? source.includes('.') ? createPathGetter(publicThis, source) : () => publicThis[source] : source.bind(publicThis, publicThis) let cb if (isFunction(value)) { cb = value } else { cb = value.handler as Function options = value } return doWatch(getter, cb.bind(publicThis), options, this)}// 获取侦听门路export function createPathGetter(ctx: any, path: string) { const segments = path.split('.') return () => { let cur = ctx for (let i = 0; i < segments.length && cur; i++) { cur = cur[segments[i]] } return cur }}// 递归遍历获取值,seen用于避免陷入死循环function traverse(value: unknown, seen: Set<unknown> = new Set()) { if ( !isObject(value) || seen.has(value) || (value as any)[ReactiveFlags.SKIP] ) { return value } seen.add(value) if (isRef(value)) { traverse(value.value, seen) } else if (isArray(value)) { for (let i = 0; i < value.length; i++) { traverse(value[i], seen) } } else if (isSet(value) || isMap(value)) { value.forEach((v: any) => { traverse(v, seen) }) } else if (isPlainObject(value)) { for (const key in value) { traverse((value as any)[key], seen) } } return value}
如果文章中有谬误之处,还望大佬们批评指正。
如果喜爱我的文章,能够关注 + 点赞。
如果须要继续理解Vue3源码剖析系列,可关注我公共号【coder狂想曲】。在这里咱们一起精进!年年double!
感激浏览。
参考:
- Vue官网
- Vue-next