vue3中watch和watchEffect的区别

 更新时间:2023年05月23日 09:38:28   作者:泡泡茶壶39  
本文主要介绍了vue3中watch和watchEffect的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Element+Vue实现动态表单多个下拉框组件功能

    Element+Vue实现动态表单多个下拉框组件功能

    这篇文章主要介绍了Element+Vue实现动态表单多个下拉框组件功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-07-07
  • vue实现图形验证码登录

    vue实现图形验证码登录

    这篇文章主要为大家详细介绍了vue实现图形验证码登录,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-04-04
  • Vite中使用Ant Design Vue3.x框架教程示例

    Vite中使用Ant Design Vue3.x框架教程示例

    这篇文章主要为大家介绍了Vite中使用Ant Design Vue3.x框架教程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • 使用vue-cli导入Element UI组件的方法

    使用vue-cli导入Element UI组件的方法

    这篇文章给大家介绍了使用vue-cli导入Element UI组件的方法,非常不错,具有一定的参考借鉴价值,需要的朋友一起看看吧
    2018-05-05
  • vue如何实现observer和watcher源码解析

    vue如何实现observer和watcher源码解析

    这篇文章主要为大家详细介绍了vue如何实现observer和watcher源码的相关资料,分析vue的observe实现源码,聊聊如何一步一步实现$watch,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-03-03
  • VUE+element开发后台管理的搜索功能

    VUE+element开发后台管理的搜索功能

    这篇文章主要为大家详细介绍了VUE+element开发后台管理的搜索功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • vue中v-cloak解决刷新或者加载出现闪烁问题(显示变量)

    vue中v-cloak解决刷新或者加载出现闪烁问题(显示变量)

    这篇文章主要介绍了vue中v-cloak解决刷新或者加载出现闪烁问题(显示变量) ,需要的朋友可以参考下
    2018-04-04
  • vue去掉严格开发,去掉vue-cli安装时的eslint或修改配置方式

    vue去掉严格开发,去掉vue-cli安装时的eslint或修改配置方式

    这篇文章主要介绍了vue去掉严格开发,去掉vue-cli安装时的eslint或修改配置方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-04-04
  • vue.js踩坑之ref引用细节点讲解

    vue.js踩坑之ref引用细节点讲解

    这篇文章主要介绍了vue.js踩坑之ref引用细节点讲解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • element upload 钩子函数的坑及解决

    element upload 钩子函数的坑及解决

    这篇文章主要介绍了element upload 钩子函数的坑及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10

最新评论