watchEffect
它立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
let count = ref(1)
const stop = watchEffect(() => console.log(count.value))
// ->log 1
let add = () => count.value++
// ->log 2
// 显式调用返回值以停止侦听
stop()
副作用刷新时机
| pre | sync | post | |
|---|---|---|---|
| 更新时机 | 组件更新前执行 | 强制效果始终同步触发 | 组件更新后执行 |
作用和 computed 差不多,可以监听使用的响应式追踪其依赖变化时执行副作用函数。但 watchEffect 没有缓存,返回值也是用于停止侦听。
watch
watch 需要侦听特定的数据源,并在回调函数中执行副作用。默认情况下,它也是惰性的,即只有当被侦听的源发生变化时才执行回调。
参数
getter 函数
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
侦听ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
侦听reative
const count = reative(0)
watch(count, (count, prevCount) => {
/* ... */
})
侦听多个数据源
const firstName = ref('')
const lastName = ref('')
watch([firstName, lastName], (newValues, prevValues) => {
console.log(newValues, prevValues)
})
侦听响应式对象
const numbers = reactive([1, 2, 3, 4])
watch(
() => [...numbers],
(numbers, prevNumbers) => {
console.log(numbers, prevNumbers)
}
)
numbers.push(5)
与 watchEffect 共享的行为
1,停止侦听
let stop1 = watchEffect(() => ...)
let stop2 = watch(count, (count, prevCount) => { ... })
stop1()
stop2()
2,清除副作用
onInvalidate 作为一个实参传入
watchEffect( onInvalidate => onInvalidate( ()=>{...} ) )
watch(count, (count, prevCount,onInvalidate) => { ...; onInvalidate(()=>...) })
onInvalidate 1,副作用即将重新执行时执行 2,watch失效回调会被触发
3,副作用刷新时机
flush: pre | sync | post
4,侦听器调试
onTrack 和 onTrigger 选项可用于调试侦听器的行为。
onTrack 将在响应式 property 或 ref 作为依赖项被追踪时被调用
onTrigger 将在依赖项变更导致副作用被触发时被调用
源码实现
watchEffect
export function watchEffect(
effect,options
) {
return doWatch(effect, null, options)
}
watch
export function watch(
source,cb,options
) {
// 执行的不为函数时,直接报错
if (__DEV__ && !isFunction(cb)) {
warn(...)
}
return doWatch(source, cb, options)
}
可以看到 watchEffect 和 watch 最大的区别在于形参的传递不同
doWatch
function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null,
{ immediate, deep, flush, onTrack, onTrigger }
) {
...
}
警告
// watchEffect 不能设置 immediate 和 deep
if (__DEV__ && !cb) {
if (immediate !== undefined) {
warn(...)
}
if (deep !== undefined) {
warn(...)
}
}
搜集数据
// 初始化数据
// 提示警告使用
const warnInvalidSource = (s: unknown) => {
warn(...)
}
const instance = null
// 返回原数据
let getter: () => any
// 是否深层遍历
let forceTrigger = false
let isMultiSource = false
if (isRef(source)) {
// 监听 ref
getter = () => source.value
forceTrigger = !!source._shallow
} else if (isReactive(source)) {
// 监听 reactive
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)) {
// traverse遍历 reactive 下的所有元素
return traverse(s)
} else if (isFunction(s)) {
// 返回执行的结果
return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
} else {
// 报异常,只能监听 响应式变量
__DEV__ && warnInvalidSource(s)
}
})
} else if (isFunction(source)) {
if (cb) {
// 一个watch
// 返回 source 的执行结果
getter = () =>
callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
} else {
// 一个watchEffect
getter = () => {
if (instance && instance.isUnmounted) {
return
}
if (cleanup) {
cleanup()
}
// 返回 watchEffect 的执行结果,传入实参 onInvalidate
return callWithAsyncErrorHandling(
source,
instance,
ErrorCodes.WATCH_CALLBACK,
[onInvalidate]
)
}
}
} else {
// 非 响应式变量 直接报警告
getter = NOOP
__DEV__ && warnInvalidSource(source)
}
// watch 且 deep 为true,使用 traverse 深层遍历让其所有 响应式元素 搜集依赖
if (cb && deep) {
const baseGetter = getter
getter = () => traverse(baseGetter())
}
- 监听的数据源必须是 响应式函数
- 监听的数据源是 reactive 会自动 deep 深层遍历,监听响应
- traverse 会遍历 reactive 下所有响应式元素,搜集依赖
执行
// 清除副作用执行
let cleanup: () => void
let onInvalidate: InvalidateCbRegistrator = (fn: () => void) => {
cleanup = effect.onStop = () => {
callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
}
}
// 初始化老值
let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
// 执行 watch 或 watchEffect
const job = () => {
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()
}
// 执行 cb 传入形参 newValue oldVaue onInvalidate
callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
newValue,
// 如果 oldValue 是初始化的 {} ,传入 undefined。第一次执行会出现这种情况
oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
onInvalidate
])
oldValue = newValue
}
} else {
// watchEffect
effect.run()
}
}
执行 watch
- deep 设置了{ deep : true } 或 reactive 下执行
- forceTrigger 1,多个数据源中有 reactive 2,ref
- 对比新老数据是否变更,Object.is 没有深层对比
flush
let scheduler: EffectScheduler
if (flush === 'sync') {
// 同步执行
scheduler = job
} else if (flush === 'post') {
// 进入异步队列,组件更新后执行
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
// 进入异步队列,组件更新前执行
scheduler = () => {
if (!instance || instance.isMounted) {
queuePreFlushCb(job)
} else {
// 如果组件还没挂载,则同步执行确保回调函数在组件挂载之前执行。
job()
}
}
}
// 设置 watch 的effect,之后触发getter中的元素被修改就触发 scheduler
const effect = new ReactiveEffect(getter, scheduler)
// 设置了 onTrack 和 onTrigger 的话
if (__DEV__) {
effect.onTrack = onTrack
effect.onTrigger = onTrigger
}
// 执行
if (cb) {
// immediate 同步执行一次 watch
if (immediate) {
job()
} else {
// 把 oldValue 设置成当前值
oldValue = effect.run()
}
} else if (flush === 'post') {
queuePostRenderEffect(
effect.run.bind(effect),
instance && instance.suspense
)
} else {
effect.run()
}
return () => {
// watch 和 watchEffect 都会接收到一个消除 effect 的函数 Stop
// 执行一次 onInvalidate
effect.stop()
// 删除当前作用域下的 effect
if (instance && instance.scope) {
remove(instance.scope.effects!, effect)
}
}
| watch | watchEffect | |
|---|---|---|
| 执行 | 惰性执行,除非immediate:true | 初始化时会运行一次 |
| 监听 | 监听指定的变量 | 监听副作用函数内的使用的响应式变量 |
| 回调参数 | NewValue,OldValue,onInvalidate | onInvalidate |
| 依赖搜集 | source中指定的数据源,如果是 reactive 自动使用 deep | 执行 fn 搜集其中使用的 响应式变量 依赖 |
依赖搜集的方式不同,使得 watch 可以在监听的数据源变化后执行。而 watchEffect 必须先执行一次收集依赖。