【vue3源碼】五魁亦、watch源碼解析
參考代碼版本:vue 3.2.37
官方文檔:https://vuejs.org/
watch用來監(jiān)聽特定數(shù)據(jù)源,并在單獨(dú)的回調(diào)函數(shù)中執(zhí)行副作用羔挡。默認(rèn)是惰性的——即回調(diào)僅在偵聽源發(fā)生變化時(shí)被調(diào)用洁奈。
文件位置:packages/runtime-core/src/apiWatch.ts
使用示例
監(jiān)聽一個(gè)getter
函數(shù):
const state = reactive({ count: 0 })
watch(
() => state.count,
(newVal, oldVal) => {
//...
}
)
監(jiān)聽一個(gè)ref
:
const count = ref(0)
watch(
count,
(newVal, oldVal) => {
//...
}
)
監(jiān)聽多個(gè)數(shù)據(jù)源:
const foo = ref('')
const bar = ref('')
watch(
[ foo, bar ],
([ newFoo, newBar ], [ oldFoo, oldBar ]) => {
// ...
}
)
深度監(jiān)聽:
const state = reactive({ count: 0 })
watch(
() => state,
() => {
// ...
},
{ deep: true }
)
// or
watch(state, () => {
// ...
})
源碼分析
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.`
)
}
return doWatch(source as any, cb, options)
}
watch
接收三個(gè)參數(shù):source
監(jiān)聽的源、cb
回調(diào)函數(shù)绞灼、options
監(jiān)聽配置利术,watch
函數(shù)返回一個(gè)停止監(jiān)聽函數(shù)。镀赌。
在watch
中調(diào)用了一個(gè)叫做doWatch
的函數(shù)氯哮,與watch
作用相似的watchEffect
际跪、watchPostEffect
商佛、watchSyncEffect
內(nèi)部也都使用了這個(gè)doWatch
函數(shù)。
export function watchEffect(
effect: WatchEffect,
options?: WatchOptionsBase
): WatchStopHandle {
return doWatch(effect, null, options)
}
export function watchPostEffect(
effect: WatchEffect,
options?: DebuggerOptions
) {
return doWatch(
effect,
null,
(__DEV__
? Object.assign(options || {}, { flush: 'post' })
: { flush: 'post' }) as WatchOptionsBase
)
}
export function watchSyncEffect(
effect: WatchEffect,
options?: DebuggerOptions
) {
return doWatch(
effect,
null,
(__DEV__
? Object.assign(options || {}, { flush: 'sync' })
: { flush: 'sync' }) as WatchOptionsBase
)
}
可見doWatch
是watch API
的核心姆打,接下來重點(diǎn)研究doWatch
的實(shí)現(xiàn)良姆。
doWatch
doWatch
源碼過長,這里就不搬運(yùn)了幔戏,在分析過程中玛追,會展示相關(guān)代碼。
doWatch
函數(shù)接收三個(gè)參數(shù):source
監(jiān)聽的數(shù)據(jù)源闲延,cb
回調(diào)函數(shù)痊剖,options
:監(jiān)聽配置。doWatch
返回一個(gè)停止監(jiān)聽函數(shù)垒玲。
function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null,
{ immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): WatchStopHandle {
// ...
}
首先需要對immediate
陆馁、deep
做校驗(yàn),如果cb
為null
合愈,immediate
叮贩、deep
不為undefined
進(jìn)行提示击狮。
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.`
)
}
// 當(dāng)前組件實(shí)例
const instance = currentInstance
// 副作用函數(shù),在初始化effect時(shí)使用
let getter: () => any
// 強(qiáng)制觸發(fā)監(jiān)聽
let forceTrigger = false
// 是否為多數(shù)據(jù)源益老。
let isMultiSource = false
然后根據(jù)傳入的soure
確定getter
彪蓬、forceTrigger
、isMultiSource
捺萌。這里分了5個(gè)分支:
- 如果
source
是ref
類型档冬,getter
是個(gè)返回source.value
的函數(shù),forceTrigger
取決于source
是否是淺層響應(yīng)式互婿。
if (isRef(source)) {
getter = () => source.value
forceTrigger = isShallow(source)
}
- 如果
source
是reactive
類型捣郊,getter
是個(gè)返回source
的函數(shù),并將deep
設(shè)置為true
慈参。
if (isReactive(source)) {
getter = () => source
deep = true
}
- 如果
source
是個(gè)數(shù)組呛牲,將isMultiSource
設(shè)為true
,forceTrigger
取決于source
是否有reactive
類型的數(shù)據(jù)驮配,getter
函數(shù)中會遍歷source
娘扩,針對不同類型的source
做不同處理。
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)
}
})
}
- 如果
source
是個(gè)function
壮锻。存在cb
的情況下琐旁,getter
函數(shù)中會執(zhí)行source
,這里source
會通過callWithErrorHandling
函數(shù)執(zhí)行猜绣,在callWithErrorHandling
中會處理source
執(zhí)行過程中出現(xiàn)的錯(cuò)誤灰殴;不存在cb
的話,在getter
中掰邢,如果組件已經(jīng)被卸載了牺陶,直接return
,否則判斷cleanup
(cleanup
是在watchEffect
中通過onCleanup
注冊的清理函數(shù))辣之,如果存在cleanup
執(zhí)行cleanup
掰伸,接著執(zhí)行source
,并返回執(zhí)行結(jié)果怀估。source
會被callWithAsyncErrorHandling
包裝狮鸭,該函數(shù)作用會處理source
執(zhí)行過程中出現(xiàn)的錯(cuò)誤,與callWithErrorHandling
不同的是多搀,callWithAsyncErrorHandling
會處理異步錯(cuò)誤歧蕉。
if (isFunction(source)) {
if (cb) {
getter = () =>
callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
} else {
// watchEffect
getter = () => {
// 如果組件實(shí)例已經(jīng)卸載,直接return
if (instance && instance.isUnmounted) {
return
}
// 如果清理函數(shù)康铭,則執(zhí)行清理函數(shù)
if (cleanup) {
cleanup()
}
// 執(zhí)行source惯退,傳入onCleanup,用來注冊清理函數(shù)
return callWithAsyncErrorHandling(
source,
instance,
ErrorCodes.WATCH_CALLBACK,
[onCleanup]
)
}
}
}
callWithErrorHandling
函數(shù)可以接收四個(gè)參數(shù):fn
待執(zhí)行的函數(shù)麻削、instance
組件實(shí)例蒸痹、type
fn執(zhí)行過程中出現(xiàn)的錯(cuò)誤類型春弥、args
fn執(zhí)行所需的參數(shù)。
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
的參數(shù)與callWithErrorHandling
類似叠荠,與callWithErrorHandling
不同的是匿沛,callWithAsyncErrorHandling
可以接受一個(gè)fn
數(shù)組。
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
}
- 其他情況榛鼎,
getter
會被賦為一個(gè)空函數(shù)
getter = NOOP
__DEV__ && warnInvalidSource(source)
接下來會對vue2
的數(shù)組的進(jìn)行兼容性處理逃呼,breaking-changes/watch
if (__COMPAT__ && cb && !deep) {
const baseGetter = getter
getter = () => {
const val = baseGetter()
if (
isArray(val) &&
checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)
) {
traverse(val)
}
return val
}
}
如果存在cb
并且deep
為true
,那么需要對數(shù)據(jù)進(jìn)行深度監(jiān)聽者娱,這時(shí)抡笼,會重新對getter
賦值,在新的getter
函數(shù)中遞歸訪問之前getter
的返回結(jié)果黄鳍。
if (cb && deep) {
const baseGetter = getter
getter = () => traverse(baseGetter())
}
traverse
實(shí)現(xiàn)推姻,遞歸遍歷所有屬性,seen
用于防止循環(huán)引用問題框沟。
export function traverse(value: unknown, seen?: Set<unknown>) {
// 如果value不是對象或value不可被轉(zhuǎn)為代理(經(jīng)過markRaw處理)藏古,直接return value
if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
return value
}
// sean用于暫存訪問過的屬性,防止出現(xiàn)循環(huán)引用的問題
// 如:
// const obj = { a: 1 }
// obj.b = obj
seen = seen || new Set()
// 如果seen中已經(jīng)存在了value忍燥,意味著value中存在循環(huán)引用的情況拧晕,這時(shí)return value
if (seen.has(value)) {
return value
}
// 添加value到seen中
seen.add(value)
// 如果是ref,遞歸訪問value.value
if (isRef(value)) {
traverse(value.value, seen)
} else if (isArray(value)) { // 如果是數(shù)組梅垄,遍歷數(shù)組并調(diào)用traverse遞歸訪問元素內(nèi)的屬性
for (let i = 0; i < value.length; i++) {
traverse(value[i], seen)
}
} else if (isSet(value) || isMap(value)) { // 如果是Set或Map厂捞,調(diào)用traverse遞歸訪問集合中的值
value.forEach((v: any) => {
traverse(v, seen)
})
} else if (isPlainObject(value)) { // 如果是原始對象,調(diào)用traverse遞歸方位value中的屬性
for (const key in value) {
traverse((value as any)[key], seen)
}
}
// 最后需要返回value
return value
}
到此队丝,getter
函數(shù)(getter
函數(shù)中會盡可能訪問響應(yīng)式數(shù)據(jù)靡馁,尤其是deep
為true
并存在cb
的情況時(shí),會調(diào)用traverse
完成對source
的遞歸屬性訪問)炭玫、forceTrigger
奈嘿、isMultiSource
已經(jīng)被確定貌虾,接下來聲明了兩個(gè)變量:cleanup
吞加、onCleanup
。onCleanup
會作為參數(shù)傳遞給watchEffect
中的effect
函數(shù)尽狠。當(dāng)onCleanup
執(zhí)行時(shí)衔憨,會將他的參數(shù)通過callWithErrorHandling
封裝賦給cleanup
及effect.onStop
(effect
在后文中創(chuàng)建)。
let cleanup: () => void
let onCleanup: OnCleanup = (fn: () => void) => {
cleanup = effect.onStop = () => {
callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
}
}
緊接著是一段SSR
處理過程:
if (__SSR__ && isInSSRComponentSetup) {
// we will also not call the invalidate callback (+ runner is not set up)
onCleanup = NOOP
if (!cb) {
getter()
} else if (immediate) {
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
getter(),
isMultiSource ? [] : undefined,
onCleanup
])
}
return NOOP
}
然后聲明了一個(gè)oldValue
和job
變量袄膏。如果是多數(shù)據(jù)源oldValue
是個(gè)數(shù)組,否則是個(gè)對象。
job
函數(shù)的作用是觸發(fā)cb
(watch
)或執(zhí)行effect.run
(watchEffect
)嗡午。job
函數(shù)中會首先判斷effect
的激活狀態(tài),如果未激活德崭,則return
。然后判斷如果存在cb
揖盘,調(diào)用effet.run
獲取最新值眉厨,下一步就是觸發(fā)cb
,這里觸發(fā)cb
需要滿足以下條件的任意一個(gè)條件即可:
- 深度監(jiān)聽
deep===true
- 強(qiáng)制觸發(fā)
forceTrigger===true
- 如果多數(shù)據(jù)源兽狭,
newValue
中存在與oldValue
中的值不相同的項(xiàng)(利用Object.is
判斷)憾股;如果不是多數(shù)據(jù)源,newValue
與oldValue
不相同箕慧。 - 開啟了
vue2
兼容模式服球,并且newValue
是個(gè)數(shù)組,并且開啟了WATCH_ARRAY
只要符合上述條件的任意一條颠焦,便可已觸發(fā)cb
斩熊,在觸發(fā)cb
之前會先調(diào)用cleanup
函數(shù)。執(zhí)行完cb
后伐庭,需要將newValue
賦值給oldValue
座享。
如果不存在cb
,那么直接調(diào)用effect.run
即可似忧。
let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
const job: SchedulerJob = () => {
if (!effect.active) {
return
}
if (cb) {
const newValue = effect.run()
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))
) {
if (cleanup) {
cleanup()
}
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
newValue,
// 如果oldValue為INITIAL_WATCHER_VALUE渣叛,說明是第一次watch,那么oldValue是undefined
oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
onCleanup
])
oldValue = newValue
}
} else {
effect.run()
}
}
job.allowRecurse = !!cb
接下來聲明了一個(gè)調(diào)度器scheduler
盯捌,在scheduler
中會根據(jù)flush
的不同決定job
的觸發(fā)時(shí)機(jī):
let scheduler: EffectScheduler
if (flush === 'sync') {
scheduler = job as any
} else if (flush === 'post') {
// 延遲執(zhí)行淳衙,將job添加到一個(gè)延遲隊(duì)列,這個(gè)隊(duì)列會在組件掛在后饺著、更新的生命周期中執(zhí)行
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
// 默認(rèn) pre箫攀,將job添加到一個(gè)優(yōu)先執(zhí)行隊(duì)列,該隊(duì)列在掛載前執(zhí)行
scheduler = () => {
if (!instance || instance.isMounted) {
queuePreFlushCb(job)
} else {
job()
}
}
}
此時(shí)幼衰,getter
與scheduler
準(zhǔn)備完成靴跛,創(chuàng)建effect
實(shí)例。
const effect = new ReactiveEffect(getter, scheduler)
創(chuàng)建effect
實(shí)例后渡嚣,開始首次執(zhí)行副作用函數(shù)梢睛。這里針對不同情況有多個(gè)分支:
- 如果存在
cb
的情況- 如果
immediate
為true
,執(zhí)行job
识椰,觸發(fā)cb
- 否則執(zhí)行
effect.run()
進(jìn)行依賴的收集绝葡,并將結(jié)果賦值給oldValue
- 如果
- 如果
flush===post
,會將effect.run
推入一個(gè)延遲隊(duì)列中 - 其他情況腹鹉,也就是
watchEffect
藏畅,則會執(zhí)行effect.run
進(jìn)行依賴的收集
if (cb) {
if (immediate) {
job()
} else {
oldValue = effect.run()
}
} else if (flush === 'post') {
queuePostRenderEffect(
effect.run.bind(effect),
instance && instance.suspense
)
} else {
effect.run()
}
最后,返回一個(gè)函數(shù)功咒,這個(gè)函數(shù)的作用是停止watch
對數(shù)據(jù)源的監(jiān)聽愉阎。在函數(shù)內(nèi)部調(diào)用effect.stop()
將effect
置為失活狀態(tài)绞蹦,如果存在組件實(shí)例,并且組件示例中存在effectScope
榜旦,那么需要將effect
從effectScope
中移除坦辟。
return () => {
effect.stop()
if (instance && instance.scope) {
remove(instance.scope.effects!, effect)
}
}
watchEffect、watchSyncEffect章办、watchPostEffect
watchEffect
锉走、watchSyncEffect
、watchPostEffect
的實(shí)現(xiàn)均是通過doWatch
實(shí)現(xiàn)藕届。
export function watchEffect(
effect: WatchEffect,
options?: WatchOptionsBase
): WatchStopHandle {
return doWatch(effect, null, options)
}
export function watchPostEffect(
effect: WatchEffect,
options?: DebuggerOptions
) {
return doWatch(
effect,
null,
(__DEV__
? Object.assign(options || {}, { flush: 'post' })
: { flush: 'post' }) as WatchOptionsBase
)
}
export function watchSyncEffect(
effect: WatchEffect,
options?: DebuggerOptions
) {
return doWatch(
effect,
null,
(__DEV__
? Object.assign(options || {}, { flush: 'sync' })
: { flush: 'sync' }) as WatchOptionsBase
)
}
watch與watchEffect的區(qū)別
watch
只會追蹤在source
中明確的數(shù)據(jù)源挪蹭,不會追蹤回調(diào)函數(shù)中訪問到的東西。而且只在數(shù)據(jù)源發(fā)生變化后觸發(fā)回調(diào)休偶。watch
會避免在發(fā)生副作用時(shí)追蹤依賴(當(dāng)發(fā)生副作用時(shí)梁厉,會執(zhí)行調(diào)度器,在調(diào)度器中會將job
推入不同的任務(wù)隊(duì)列踏兜,達(dá)到控制回調(diào)函數(shù)的觸發(fā)時(shí)機(jī)的目的)词顾,因此,我們能更加精確地控制回調(diào)函數(shù)的觸發(fā)時(shí)機(jī)碱妆。
watchEffect
肉盹,會在副作用發(fā)生期間追蹤依賴。它會在同步執(zhí)行過程中疹尾,自動(dòng)追蹤所有能訪問到的響應(yīng)式property
示例分析
為了更好地理解watch
及watchEffect
的流程上忍,我們以下面幾個(gè)例子來理解watch
及watchEffect
。
例1
const state = reactive({ str: 'foo', obj: { num: 1 } })
const flag = ref(true)
watch(
[ flag, () => state.obj ],
([ newFlag, newObj ], [ oldFlag, oldObj ]) => {
console.log(newFlag)
console.log(newObj.num)
console.log(oldFlag)
console.log(oldObj && oldObj.num)
},
{
immediate: true,
flush: 'sync'
}
)
state.obj.num = 2
state.obj = {
num: 2
}
在watch
中調(diào)用doWatch
方法纳本,在doWatch
會構(gòu)造getter
函數(shù)窍蓝,因?yàn)樗O(jiān)聽的數(shù)據(jù)源是個(gè)數(shù)組,所以getter
函數(shù)返回值也是個(gè)數(shù)組繁成,因?yàn)閿?shù)據(jù)源的第一項(xiàng)是個(gè)ref
吓笙,所以getter
返回值第一項(xiàng)是ref.value
,數(shù)據(jù)源的第二項(xiàng)是個(gè)function
巾腕,所以getter
返回值第二項(xiàng)是() => state.obj
的返回值面睛,也就是state.obj
,由于我們未指定depp
祠墅,最終生成的getter
是() => [ref.value, state.obj]
侮穿。
然后利用getter
與scheduler
生成effect
歌径,因?yàn)槲覀冎付?code>immediate: true毁嗦,所以會立即執(zhí)行job
函數(shù),在job
函數(shù)中回铛,會執(zhí)行effect.run()
(這個(gè)過程中最終執(zhí)行getter
函數(shù)狗准,而在執(zhí)行getter
函數(shù)的過程中會被對應(yīng)響應(yīng)式對象的proxy
所攔截克锣,進(jìn)而收集依賴),然后將effect.run()
的結(jié)果賦值給newValue
腔长。然后對位比較newValue
與oldValue
中的元素袭祟,因?yàn)?code>oldValue此時(shí)是個(gè)空數(shù)組,所以會觸發(fā)cb
捞附,在cb
觸發(fā)過程中將newValue
巾乳、oldValue
依次傳入,此時(shí)打印true 1 undefined undefined
鸟召,當(dāng)cb
執(zhí)行完胆绊,將newValue
賦值為oldValue
。
當(dāng)執(zhí)行state.obj.num = 2
時(shí)欧募。因?yàn)樵谏弦淮蔚囊蕾囀占^程中(也就是getter
執(zhí)行過程中)压状,并沒有訪問到num
屬性,也就不會收集它的依賴跟继,所以該步驟不會影響到watch
种冬。
當(dāng)state.obj = { num: 2 }
時(shí),會觸發(fā)到obj
對應(yīng)的依賴舔糖,而在依賴觸發(fā)過程中會執(zhí)行調(diào)度器娱两,因?yàn)?code>flush為sync
,所以調(diào)度器就是job
金吗,當(dāng)執(zhí)行job
時(shí)谷婆,通過effect.run()
得到newValue
,因?yàn)檫@時(shí)oldValue
中的state.value
與newValue
中的state.value
已經(jīng)不是同一個(gè)對象了辽聊,所以觸發(fā)cb
纪挎。打印true 2 true 2
。
為什么第二次打印newObj.num
與oldObj.num
相同跟匆?因?yàn)?code>oldValue中的oldObj
保存的是state.obj
的引用地址异袄,一旦state.obj
發(fā)生改變,oldValue
也會對應(yīng)改變玛臂。
例2
const state = reactive({ str: 'foo', obj: { num: 1 } })
const flag = ref(true)
watchEffect(() => {
console.log(flag.value)
console.log(state.obj.num)
})
state.obj.num = 2
state.obj = {
num: 3
}
與例1相同烤蜕,例2先生成getter
(getter
中會調(diào)用source
)與scheduler
,然后生成effect
迹冤。因?yàn)?code>watchEffect是沒有cb
參數(shù)讽营,也未指定flush
,所以會直接執(zhí)行effct.run()
泡徙。在effect.run
執(zhí)行過程中橱鹏,會調(diào)用source
,在source
執(zhí)行過程中會將effect
收集到flag.dep
及targetMap[toRaw(state)].obj
、targetMap[toRaw(state).obj].num
中莉兰。所以第一次打印true 1
挑围。
當(dāng)執(zhí)行state.obj.num = 2
,會觸發(fā)targetMap[toRaw(state).obj].num
中的依賴糖荒,也就是effect
杉辙,在觸發(fā)依賴過程中會執(zhí)行effect.scheduler
,將job
推入一個(gè)pendingPreFlushCbs
隊(duì)列中捶朵。
當(dāng)執(zhí)行state.obj = { num: 3 }
蜘矢,會觸發(fā)targetMap[toRaw(state)].obj
中的依賴,也就是effect
综看,在觸發(fā)依賴過程中會執(zhí)行effect.scheduler
硼端,將job
推入一個(gè)pendingPreFlushCbs
隊(duì)列中。
最后會執(zhí)行pendingPreFlushCbs
隊(duì)列中的job
寓搬,在執(zhí)行之前會對pendingPreFlushCbs
進(jìn)行去重珍昨,也就是說最后只會執(zhí)行一個(gè)job
。最終打印true 3
句喷。
總結(jié)
watch
镣典、watchEffect
、watchSyncEffect
唾琼、watchPostEffect
的實(shí)現(xiàn)均是通過一個(gè)doWatch
函數(shù)實(shí)現(xiàn)兄春。
dowatch
中會首先生成一個(gè)getter
函數(shù)。如果是watch
API锡溯,那么這個(gè)getter
函數(shù)中會根據(jù)傳入?yún)?shù)赶舆,訪問監(jiān)聽數(shù)據(jù)源中的屬性(可能會遞歸訪問對象中的屬性,取決于deep
)祭饭,并返回與數(shù)據(jù)源數(shù)據(jù)類型一致的數(shù)據(jù)(如果數(shù)據(jù)源是ref
類型芜茵,getter
函數(shù)返回ref.value
;如果數(shù)據(jù)源類型是reactive
倡蝙,getter
函數(shù)返回值也是reactive
九串;如果數(shù)據(jù)源是數(shù)組,那么getter
函數(shù)返回值也應(yīng)該是數(shù)組寺鸥;如果數(shù)據(jù)源是函數(shù)類型猪钮,那么getter
函數(shù)返回值是數(shù)據(jù)源的返回值)。如果是watchEffect
等API胆建,那么getter
函數(shù)中會執(zhí)行source
函數(shù)烤低。
然后定義一個(gè)job
函數(shù)。如果是watch
笆载,job
函數(shù)中會執(zhí)行effect.run
獲取新的值扑馁,并比較新舊值涯呻,是否執(zhí)行cb
;如果是watchEffect
等API檐蚜,job
中執(zhí)行effect.run
魄懂。那么如何只監(jiān)聽到state.obj.num
的變換呢沿侈?
當(dāng)聲明完job
闯第,會緊跟著定義一個(gè)調(diào)度器,這個(gè)調(diào)度器的作用是根據(jù)flush
將job
放到不同的任務(wù)隊(duì)列中缀拭。
然后根據(jù)getter
與調(diào)度器
scheduler初始化一個(gè)
ReactiveEffect`實(shí)例咳短。
接著進(jìn)行初始化:如果是watch
,如果是立即執(zhí)行蛛淋,則馬上執(zhí)行job
咙好,否則執(zhí)行effect.run
更新oldValue
;如果flush
是post
褐荷,會將effect.run
函數(shù)放到延遲隊(duì)列中延遲執(zhí)行勾效;其他情況執(zhí)行effect.run
。
最后返回一個(gè)停止watch
的函數(shù)叛甫。