vue3中watch和watchEffect的区别
作者:泡泡茶壶39
watch和watchEffect
watchEffect()
官网介绍:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
类型
function watchEffect( effect: (onCleanup: OnCleanup) => void, options?: WatchEffectOptions ): StopHandle type OnCleanup = (cleanupFn: () => void) => void interface WatchEffectOptions { flush?: 'pre' | 'post' | 'sync' // 默认:'pre' onTrack?: (event: DebuggerEvent) => void onTrigger?: (event: DebuggerEvent) => void } type StopHandle = () => void
详细信息
- 第一个参数就是要运行的副作用函数。这个副作用函数的参数也是一个函数,用来注册清理回调。清理回调会在该副作用下一次执行前被调用,可以用来清理无效的副作用,例如等待中的异步请求 (参见下面的示例)。
- 第二个参数是一个可选的选项,可以用来调整副作用的刷新时机或调试副作用的依赖。
默认情况下,侦听器将在组件渲染之前执行。设置 flush: 'post' 将会使侦听器延迟到组件渲染之后再执行。详见回调的触发时机。在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。这可以通过设置 flush: 'sync' 来实现。然而,该设置应谨慎使用,因为如果有多个属性同时更新,这将导致一些性能和数据一致性的问题。
返回值是一个用来停止该副作用的函数。
示例
const count = ref(0) watchEffect(() => console.log(count.value)) // -> 输出 0 count.value++ // -> 输出 1
副作用清除:
watchEffect(async (onCleanup) => { const { response, cancel } = doAsyncWork(id.value) // `cancel` 会在 `id` 更改时调用 // 以便取消之前 // 未完成的请求 onCleanup(cancel) data.value = await response })
停止侦听器:
const stop = watchEffect(() => {}) // 当不再需要此侦听器时: stop()
选项:
watchEffect(() => {}, { flush: 'post' })
功能及其使用官网写的清楚,再来了解下它的原理
function watchEffect( source: WatchEffect, options?: WatchOptionsBase ): WatchStopHandle { let getter = () => { if (cleanup) { cleanup() } return source(onCleanup) } let cleanup: () => void let onCleanup: OnCleanup = (fn: () => void) => { cleanup = effect.onStop = () => fn() } const job: SchedulerJob = () => { if (!effect.active) { return } effect.run() } let scheduler: EffectScheduler if (flush === 'sync') { scheduler = job as any } else if (flush === 'post') { scheduler = () => queuePostRenderEffect(job) } else { // default: 'pre' scheduler = () => queuePreFlushCb(job) } // 创建 effect const effect = new ReactiveEffect(getter, scheduler) if (flush === 'post') { queuePostRenderEffect( effect.run.bind(effect) ) } else { effect.run() } return () => { effect.stop() } }
watchEffect的回调函数就是一个副作用函数,因为我们使用watchEffect就是侦听到依赖的变化后执行某些操作。什么是副作用(side effect),简单的说副作用就是执行某种操作,如对外部可变数据或变量的修改,外部接口的调用等。
watchEffect内部是创建了一个ReactiveEffect对象,接收两个参数,第一个getter,在执行effect.run()时会调用,第二个scheduler在依赖变化后执行。
那我们执行watchEffect(() => console.log(count))时,具体做了什么呢,首先创建了个getter,执行getter做了两件事,第一个会看有cleanup没,cleanup会在调用OnCleanup赋值, 有就执行,第二个执行source,为watchEffect传递的回调函数,参数为OnCleanup;然后会创建OnCleanup,这个会在执行source时当参数传入,当在source里执行这个参数,会创建cleanup,这个cleanup的执行时机是下一次执行前被调用或停止该副作用时调用;然后创建scheduler,scheduler主要就是执行effect.run()也就是getter,会根据传的flush的不同会创建三种执行时机不同的scheduler 第一种sync scheduler:为异步执行,会在依赖改变时立即执行,比如for循环改变10次依赖就会执行10次 第二种 post scheduler:会在组件渲染后执行 第三种 pre scheduler:默认,会在组件渲染前执行 然后会创建ReactiveEffect,参数为之前创建getter和scheduler,创建完后接着会根据传的flush来执行effect.run(),是post会延迟在组件渲染后执行,否则就立即执行,执行effect.run()会触发依赖手收集, 最后返回停止该副作用的函数,执行会执行effect.stop(),这个会触发cleanup
wath()
官网介绍:侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。
类型
// 侦听单个来源 function watch<T>( source: WatchSource<T>, callback: WatchCallback<T>, options?: WatchOptions ): StopHandle // 侦听多个来源 function watch<T>( sources: WatchSource<T>[], callback: WatchCallback<T[]>, options?: WatchOptions ): StopHandle type WatchCallback<T> = ( value: T, oldValue: T, onCleanup: (cleanupFn: () => void) => void ) => void type WatchSource<T> = | Ref<T> // ref | (() => T) // getter | T extends object ? T : never // 响应式对象 interface WatchOptions extends WatchEffectOptions { immediate?: boolean // 默认:false deep?: boolean // 默认:false flush?: 'pre' | 'post' | 'sync' // 默认:'pre' onTrack?: (event: DebuggerEvent) => void onTrigger?: (event: DebuggerEvent) => void }
详细信息
watch() 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。
第一个参数是侦听器的源。这个来源可以是以下几种:
- 一个函数,返回一个值
- 一个 ref
- 一个响应式对象
- ...或是由以上类型的值组成的数组
第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。
当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。
第三个可选的参数是一个对象,支持以下这些选项:
- immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined。
- deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器。
- flush:调整回调函数的刷新时机。参考回调的刷新时机及 watchEffect()。
- onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器。
与 watchEffect() 相比,watch() 使我们可以:
- 懒执行副作用;
- 更加明确是应该由哪个状态触发侦听器重新执行;
- 可以访问所侦听状态的前一个值和当前值。
示例
侦听一个 getter 函数:
const state = reactive({ count: 0 }) watch( () => state.count, (count, prevCount) => { /* ... */ } )
侦听一个 ref:
const count = ref(0) watch(count, (count, prevCount) => { /* ... */ })
当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值:
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => { /* ... */ })
当使用 getter 函数作为源时,回调只在此函数的返回值变化时才会触发。如果你想让回调在深层级变更时也能触发,你需要使用 { deep: true } 强制侦听器进入深层级模式。在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。
const state = reactive({ count: 0 }) watch( () => state, (newValue, oldValue) => { // newValue === oldValue }, { deep: true } )
当直接侦听一个响应式对象时,侦听器会自动启用深层模式:
const state = reactive({ count: 0 }) watch(state, () => { /* 深层级变更状态所触发的回调 */ })
watch的功能是完全包含watchEffect的,原理其实是差不多的,主要就是getter和scheduler不同
function watch( source: WatchSource | WatchSource[] | WatchEffect | object, cb: WatchCallback | null, { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ ): WatchStopHandle { let getter: () => any if (isRef(source)) { getter = () => source.value } else if (isReactive(source)) { getter = () => source deep = true } else if (isArray(source)) { getter = () => source.map(s => { if (isRef(s)) { return s.value } else if (isReactive(s)) { return traverse(s) } else if (isFunction(s)) { return s() } }) } else if (isFunction(source)) { getter = () => source() } if (cb && deep) { const baseGetter = getter getter = () => traverse(baseGetter()) } let cleanup: () => void let onCleanup: OnCleanup = (fn: () => void) => { cleanup = effect.onStop = () => { fn() } } let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE const job: SchedulerJob = () => { if (!effect.active) { return } // watch(source, cb) const newValue = effect.run() if (cleanup) { cleanup() } cb(newValue, oldValue, onCleanup) oldValue = newValue } let scheduler: EffectScheduler if (flush === 'sync') { scheduler = job as any } else if (flush === 'post') { scheduler = () => queuePostRenderEffect(job) } else { // default: 'pre' scheduler = () => queuePreFlushCb(job) } const effect = new ReactiveEffect(getter, scheduler) if (immediate) { job() } else { oldValue = effect.run() } return () => { effect.stop() } }
首先是getter,可以看到会根据不同source的类型定义不同的getter,包括Ref、Reactive、Array和function四种,getter就是获取这些类型返回的值,从而触发依赖收集;然后是deep,如果deep为true,会改变getter为() => traverse(baseGetter()),traverse()会递归获取里面的值;onCleanup和watchEffect的一样;scheduler的执行时机和watchEffect一样,主要就是job不一样,job主要做两件事,第一个是执行cleanup和watchEffect一样,第二个是执行watch(source, cb)的第二个参数cb,参数为newValue和oldValue,newValue为effect.run()也就是getter()的返回值,oldValue为上次执行的newValue;同样也创建了ReactiveEffect对象;然后是immediate,为true执行job,job里会执行effect.run()收集依赖和cb,否则就执行effect.run()收集依赖了;最后watch的返回值和watchEffect一样。
总的来说watch和watchEffect功能很强大,源码看起来也不是很难,看完源码使用起来就更加得心应手了。
本文主要记录看watch和watchEffect源码的一些笔记,没有涉及响应式的收集依赖和触发。
参考vue3官网
到此这篇关于vue3中watch和watchEffect的区别的文章就介绍到这了,更多相关vue3 watch watchEffect内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!