• home > webfront > ECMAS > vue3 >

    vue2升级vue3:vue3中的watch和watchEffect细讲

    Author:zhoulujun Date:

    之前在vue2写过:《vue中methods watch computed对比分析,watch及computed原理挖掘》、《watch性能优化:vue watch对象键值说明-immediat

    之前在vue2写过:《vue中methods/watch/computed对比分析,watch及computed原理挖掘》、《watch性能优化:vue watch对象键值说明-immediate属性详解》,vue3的watch 与vue2 其实大同小异,但是还是细讲一下。

    vue3 监听数据不止有watch,还有watchEffect(写法和用法都有区别!)

    watch

    首先看官方文档:

    https://vuejs.org/api/reactivity-core.html#watch

    https://cn.vuejs.org/api/reactivity-core.html#watch

    // 侦听单个来源
    function watch<T>(
      source: WatchSource<T>,
      callback: WatchCallback<T>,
      options?: WatchOptions
    ): StopHandle
    
    // 侦听多个来源
    function watch<T>(
      sources: WatchSource<T>[],
      callback: WatchCallback<T[]>,
      options?: WatchOptions
    ): StopHandle
    
    // 监听属性设置
    interface WatchOptions extends WatchEffectOptions {
        deep?: boolean // 默认:false ,当需要对响应式对象进行深度监听时,设置deep: true。
        immediate?: boolean // 默认:false(watch是惰性的),需要在初始化执行回调函数需要设置为true
        flush?: string // 默认:'pre'(指定的回调应该在渲染前被调用),post 值是可以用来将回调推迟到渲染之后的。如果回调需要通过 $refs 访问更新的 DOM 或子组件,那么则使用该值。如果 flush 被设置为 sync,一旦值发生了变化,回调将被同步调用(少用,影响性能)。


    • 第一个参数(WatcherSource)是侦听器的源。这个来源可以是以下几种:

      • 一个函数,返回一个值,例如: watch(  () => state.count,(count, prevCount) => {})

      • 一个 ref

      • 一个响应式对象

      • ...或是由以上类型的值组成的数组

    • 第二个参数(Callback)是在发生变化时要调用的回调函数。这个回调函数接受三个参数:

      • 新值

      • 旧值

      • 用于注册副作用清理的回调函数。

          该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。

          当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。

    • 第三个参数(WatchOptions)可选的参数是一个对象,支持以下这些选项:

      • immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined。

      • deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器。

      • flush:调整回调函数的刷新时机。参考回调的刷新时机及 watchEffect()。

        • 默认值是 pre,指定的回调应该在渲染前被调用。

        • post 值是可以用来将回调推迟到渲染之后的。如果回调需要通过 $refs 访问更新的 DOM 或子组件,那么则使用该值。

        • 如果 flush 被设置为 sync,一旦值发生了变化,回调将被同步调用(少用,影响性能)。

      • onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器。


    watch监听多个数据

    监听单个数据源没有啥好讲的:

    const state = reactive({ count: 0 })
    watch(() => state.count, (count, prevCount) => {})
    const count = ref(0)
    watch(count, (count, prevCount) => {})

    当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值:

    watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
      /* ... */
    })

    这里要确保数据对于的顺序

    停止watch

    同步语句创建的监听器,会自动绑定到组件实例上,并且会在组件卸载时自动停止,但是,如果我们在异步回调里创建一个监听器,那它就不会绑定到当前组件上,必须手动去停止,防止内存泄漏。 

    那怎么去停止呢,其实我们只需要调用一下watch或watchEffect返回的函数

    // 直接侦听ref
    const ageRef = ref(16)
    const stopAgeWatcher = watch(ageRef, (value, oldValue) => {
      console.log(value, oldValue)
      if (value > 18) {
        stopAgeWatcher() // 当ageRef大于18,停止侦听
      }
    })

    停止watchEffect代码雷同

    Watch使用注意事项:

    当将引用对象采用ref形式定义时,如果不加上deep:true,watch是侦听不到值的变化的;而加上deep:true,watch可以侦听到数据的变化,但是当前值和先前值一样,即不能获取先前值。当将引用对象采用reactive形式定义时,不作任何处理,watch可以侦听到数据的变化,但是当前值和先前值一样。两种定义下,把watch的数据源写成getter函数的形式并进行深拷贝返回,可以在watch回调中同时获得当前值和先前值。

    结论: 当我们使用watch侦听引用对象时

    • 若使用ref定义的引用对象:

      • 只要获取当前值,watch第一个参数直接写成数据源,另外需要加上deep:true选项

      • 若要获取当前值和先前值,需要把数据源写成getter函数的形式,并且需对数据源进行深拷贝

    • 若使用reactive定义的引用对象:

      • 只要获取当前值,watch第一个参数直接写成数据源,可以不加deep:true选项

      • 若要获取当前值和先前值,需要把数据源写成getter函数的形式,并且需对数据源进行深拷贝

    来源:Vue3中watch的最佳实践 https://juejin.cn/post/6980987158710452231


    watchEffect

    官网:https://cn.vuejs.org/api/reactivity-core.html#watcheffect

    立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。

    使用watchEffect 我们并不需要传入一个特定的依赖源,而且它会立即执行一遍回调函数,如果函数产生了副作用,那它就会自动追踪副作用的依赖关系,自动分析出响应源。

    当任何你有用到的响应式依赖更新时,该回调函数便会重新执行(computed 其实类似一个带输出的同步版本的 watchEffect)。

    什么是副作用(side effect)

    简单的说副作用就是执行某种操作,如对外部可变数据或变量的修改,外部接口的调用

    • 副作用函:会产生副作用的函数被称为副作用函数

    • 副作用呢:如果几个函数的运行,可能会影响到其他函数或变量,那么这种影响就是一种副作用。

    我们来看两个例子:

    function changeText(text: string) {
      document.body.innerText = text
    }
    
    function getText() {
      return document.body.innerText
    }

    changeText函数会修改body的内容。getText会返回body的内容。如果我们使用changeText修改了body内容,那么会影响到getText获取内容,那么这时changeText就是个副作用函数。副作用函数不一定非要对某些函数产生副作用,如果一个函数修改了全局变量,这其实也是个副作用函数。

    var flag = true
    function changeFlag() {
      flag = !flag
    }

    changeFlag函数会更改一个全局变量flag,那么这也是一种副作用,所以changeText也是个副作用函数。

    来源:认识副作用函数与响应式数据 https://segmentfault.com/a/1190000042037131

    watchEffect的回调函数就是一个副作用函数,因为我们使用watchEffect就是侦听到依赖的变化后执行某些操作。


    watch和watchEffect的区别

    1. watch是惰性执行的,而watchEffect不是,不考虑watch第三个配置参数的情况下,watch在组件第一次执行的时候是不会执行的,只有在之后依赖项变化的时候再执行,而watchEffect是在程序执行到此处的时候就会立即执行,而后再响应其依赖变化执行。

    2. watch需要传递监听的对象,watchEffect不需要——watchEffect 无法访问侦听数据的新值和旧值。



    参考类:

    https://www.thisdot.co/blog/vue-3-composition-api-watch-and-watcheffect 

    https://www.thisdot.co/blog/understanding-side-effects-in-vuejs

    Vue3中watch的最佳实践 https://juejin.cn/post/6980987158710452231

    vue3之watch和watchEffect实战总结 https://juejin.cn/post/7124948096798162957






    转载本站文章《vue2升级vue3:vue3中的watch和watchEffect细讲》,
    请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/vue3/8878.html