[Vue 源码] Vue 3.2 - watch 原理
代码运行结果
代码示例
<script src="./dist/vue.global.js"></script>
<body>
<div id="app"></div>
<script>
let { reactive, watch } = Vue
let state = reactive({ age: 0 })
watch(
() => state.age,
(newValue, oldValue) => {
app.innerHTML = `count is ${newValue} `
}, {flush: "sync"}
)
setInterval(() => {
state.age++
}, 1000)
</script>
挂载阶段
第一:第一篇文章详细介绍了 reactive 创建响应式流程,我们这里默认已经创建好了响应式对象 obj。 [Vue 源码] Vue 3.2 - Reactive 原理
执行 第二句代码:调用 watch 函数。
watch(
() => state.age,
(newValue, oldValue) => {
app.innerHTML = `count is ${newValue} `
}, {flush: "sync"}
)
第一:watch 函数调用了 doWatch 函数,
export function watch<T = any, Immediate extends Readonly<boolean> = false>(
source: T | WatchSource<T>,
cb: any,
options?: WatchOptions<Immediate>
): WatchStopHandle {
return doWatch(source as any, cb, options)
}
第二:调用 doWatch 函数, 下面是 doWatch 函数流程
- 在 doWatch 函数中 初始化 getter
// 初始化 getter
if (isRef(source)) {
getter = () => source.value
forceTrigger = isShallow(source)
} else if (isReactive(source)) {
getter = () => source
deep = true
}else if (isFunction(source)) {
if (cb) {
// getter with cb
getter = () =>
source()
}
}
- 如果是 reactive 响应式对象,则需要深度监听,deep = true
- 初始化清理函数 clearUp 可以执行上一个 watch 的 cb 函数
// 清理函数
let cleanup: () => void
let onCleanup: OnCleanup = (fn: () => void) => {
cleanup = effect.onStop = () => {
callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
}
}
- 初始化 job 函数
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)) ||
(__COMPAT__ &&
isArray(newValue) &&
isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance))
) {
// cleanup before running cb again
if (cleanup) {
cleanup()
}
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
newValue,
// pass undefined as the old value when it's changed for the first time
oldValue === INITIAL_WATCHER_VALUE
? undefined
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
? []
: oldValue,
onCleanup
])
oldValue = newValue
}
}
}
- job 函数 作为 ReactEffective 对象的 scheduler 函数。
let scheduler: EffectScheduler
if (flush === 'sync') {
scheduler = job as any // the scheduler function gets called directly
}
const effect = new ReactiveEffect(getter, scheduler)
- 调用 ReactiveEffect 对象的 run 方法
- cleanEffect 清理依赖列表
- 将 activeEffects 置为 watch 创建的 ReactiveEffect 对象
- 调用 getter 方法,也就是
() => state.num
, 继而触发 state 响应式对象的getter 方法,将依赖收集到 num 属性的依赖列表里{num: [ReactiveEffect]}
- Reflect.get 返回值,成功初始化 oldValue。
- 返回 Unwatch 函数。
// initial run
oldValue = effect.run()
const unwatch = () => {
effect.stop()
if (instance && instance.scope) {
remove(instance.scope.effects!, effect)
}
}
)
return unwatch
}
自此 挂载阶段完毕
更新阶段
第一:调用 setInterval(() => { state.age++ }, 1000)
state.age 触发响应式的 setter 操作。
第二:
- setter 函数中, 通过 Reflect.set() 修改 state.age值。
- 继而触发 trigger 操作,不过这一次并没有执行该 ReactiveEffect 对象的run 方法,而是优先执行了 scheduler 方法。
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
第三:scheduler 方法中执行 job 函数(挂载的时候)
第四:job 函数中执行 effect.run() 函数.
- 再次触发 getter 拿到最新值 newValue。
- 调用 cleanUp 清理函数。
- 请用 cb 函数,传入 oldValue 和 newValue
- 等待下一次更新时再返回第一步。
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)) ||
(__COMPAT__ &&
isArray(newValue) &&
isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance))
) {
// cleanup before running cb again
if (cleanup) {
cleanup()
}
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
newValue,
// pass undefined as the old value when it's changed for the first time
oldValue === INITIAL_WATCHER_VALUE
? undefined
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
? []
: oldValue,
onCleanup
])
oldValue = newValue
}
}
}
自此更新完毕。
转载自:https://juejin.cn/post/7208910021970509884