vue3 学习5 - watch watchEffect函数

74 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 28 天,点击查看活动详情

start

  • 今天学习一下 vue3中的 watch的用法

vue2中的 watch

在学习vue3的watch的配置的时候,先复习一下 vue2中 watch的用法

<script>
export default {
  data() {
    return {
      a: 1,
      b: 2,
      c: {
        d: 4,
      },
      e: 5,
      f: 6,
    }
  },
  watch: {
    // 1. 侦听根级属性
    a(val, oldVal) {
      console.log(`new: ${val}, old: ${oldVal}`)
    },
    // 2. 字符串方法名称
    b: 'someMethod',
    // 3. 该回调将会在被侦听的对象的属性改变时调动,无论其被嵌套多深
    c: {
      handler(val, oldVal) {
        console.log('c changed')
      },
      deep: true,
    },
    // 4. 侦听单个嵌套属性:
    'c.d': function (val, oldVal) {
      // do something
    },
    // 5. 该回调将会在侦听开始之后立即调用
    e: {
      handler(val, oldVal) {
        console.log('e changed')
      },
      immediate: true,
    },
    // 6. 你可以传入回调数组,它们将会被逐一调用
    f: [
      'handle1',
      function handle2(val, oldVal) {
        console.log('handle2 triggered')
      },
      {
        handler: function handle3(val, oldVal) {
          console.log('handle3 triggered')
        },
        /* ... */
      },
    ],
  },
  methods: {
    someMethod() {
      console.log('b changed')
    },
    handle1() {
      console.log('handle 1 triggered')
    },
  },
}
</script>

简单总结一下,

  • 传入对象的形式,对象的属性名为需要监听的数据
  • 属性值为一个对象,包含handler触发的事件;deep 是否深度监听;immediate 是否首次加载;

vue3中的watch

1.监听单个 ref:正常监听

var a = ref(0)

watch(a, (newVal, oldVal) => {
    console.log(newVal, oldVal)
   
})


/*  1. 修改 a 由 0 变为 1 */
// 1 0 

2.监听多个 ref:数组的形式

var a = ref('0')
var b = ref('1')

watch([a, b], (newVal, oldVal) => {
    console.log(newVal, oldVal)
    
})

/*  1. 修改 a 由 0 变为 01 */
// ['01', 1]  [0, 1]

3.直接监听 reactive:

var a = reactive({ name: '1', hobby: { game: 'cs' } })

watch(a, (newVal, oldVal) => {
console.log(newVal, oldVal)
})

/*  1. 修改 a.name 由 1 变为 12 */
// {name: '12',hobby: { game: 'cs' }}  {name: '12',hobby: { game: 'cs' }}
// !! 注意这里的 oldVal 失效了


/*  2. 修改 a.hobby.game 由 cs 变为 csgo */
// {name: '1',hobby: { game: 'csgo' }}  {name: '1',hobby: { game: 'csgo' }}
// !! 注意这里默认开启了深度监听,及deep:true

4.监听 reactive的属性

var a = reactive({ name: '1', hobby: { game: 'cs' } })

watch('a.name', (newVal, oldVal) => {
    console.log(newVal, oldVal)
})
// 和选项式的 watch 不一样,组合式的 watch 直接 `对象.属性 ,不生效

watch(
    () => a.name,
    (newVal, oldVal) => {
        console.log(newVal, oldVal)
    }
)
/* 1. 修改 a.name 由 1 变为 12 */
// 12 1
// 采用函数返回值的形式去实现,成功监听

watch(
    () => {
        return a.hobby
    },
    (newVal, oldVal) => {
        console.log(newVal, oldVal)
    }
)
/* 2. 修改 a.hobby.game 由 cs 变为 csgo */
// 无反应

watch(
    () => {
        return a.hobby
    },
    (newVal, oldVal) => {
        console.log(newVal, oldVal)
    },
    { deep: true }
)

/* 3. 添加上配置项{ deep: true }; 修改 a.hobby.game 由 cs 变为 csgo ;*/
// {game: 'cs'} {game: 'csgo'}
// 添加上 { deep: true } 才能深度监听。

watch(
    () => {
        return a.hobby
    },
    (newVal, oldVal) => {
        console.log(newVal, oldVal)
    },
    { immediate: true }
)

/* 4. 添加上配置项{ immediate: true }; */
// {game: 'cs'} undefined
// 立即执行

watch(
    () => {
        return [a.name, a.hobby]
    },
    (newVal, oldVal) => {
        console.log(newVal, oldVal)
    },
    { deep: true }
)

/* 5. 修改 a.name 由 1 变为 12 */
//  ['12', {game: 'cs'}] (2) ['1', {game: 'cs'}]
// 可以是 一个函数,函数的返回值是一个数组
watch(
    [
        () => {
            return a.name
        },
        () => {
            return a.hobby
        },
    ],
    (newVal, oldVal) => {
        console.log(newVal, oldVal)
    },
    { deep: true }
)

/* 6. 修改 a.name 由 1 变为 12 */
//  ['12', {game: 'cs'}] (2) ['1', {game: 'cs'}]
// 也可以是 一个数组,数组中多个函数的返回值

vue3 watch的源码浅显的读一下

上面总结了一大堆,说实话,不方便记忆,我们直接在 watch 前加一个 debugger,利用浏览器的调试工具,看一看vue3中有关这个 watch的源码;

1.watch

function watch(source, cb, options) {
  // 1.判断第二个参数是不是函数
  if (process.env.NODE_ENV !== 'production' && !isFunction(cb)) {
    warn(
      `\`watch(fn, options?)\` signature has been moved to a separate API. ` +
        `Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
        `supports \`watch(source, cb, options?) signature.`
    )
  }

  // 2.执行 doWatch 这个函数,参数和我们传入 watch 相同
  return doWatch(source, cb, options)
}

2.doWatch

function doWatch(
  source,
  cb,
  { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ
) {
  /* ...省略其他代码 */
  const instance = currentInstance
  let getter
  let forceTrigger = false
  let isMultiSource = false

  // 1. 判断传入的第一个参数 是否是 ref
  if (isRef(source)) {
    // 1.1 如果是ref, get直接获取 .value的值
    getter = () => source.value
    forceTrigger = isShallow$1(source)
    // 2.判断传入的第一个参数 是否是 reactive
  } else if (isReactive(source)) {
    // 2.1 如果是 reactive, get直接获取传入的数据
    getter = () => source

    // 2.2 直接传入一个响应式对象,默认开启了deep。 **验证了上述示例的 `3.直接监听 reactive:`**
    deep = true
    // 3. 判断传入的第一个参数 是否是数组
  } else if (isArray(source)) {
    // 3.1 传入数组 标识 isMultiSource有多个参数需要监听
    isMultiSource = true
    forceTrigger = source.some((s) => isReactive(s) || isShallow$1(s))

    // 3.2 可以看到这里又对 getter做了处理,直接一个map
    getter = () =>
      source.map((s) => {
        // 3.3 这里可以看到 分别对  ref;reactive;函数;其他;都做了处理,后续试一试大杂烩  !  (具体的函数实现暂时不细究了)
        if (isRef(s)) {
          return s.value
        } else if (isReactive(s)) {
          return traverse(s)
        } else if (isFunction(s)) {
          return callWithErrorHandling(s, instance, 2 /* WATCH_GETTER */)
        } else {
          process.env.NODE_ENV !== 'production' && warnInvalidSource(s)
        }
      })
    // 4. 判断传入的第一个参数 是否是函数
  } else if (isFunction(source)) {
    // 4.1 如果是函数 使用 callWithErrorHandling处理
    if (cb) {
      // getter with cb
      getter = () =>
        callWithErrorHandling(source, instance, 2 /* WATCH_GETTER */)
    } else {
      // no cb -> simple effect
      getter = () => {
        if (instance && instance.isUnmounted) {
          return
        }
        if (cleanup) {
          cleanup()
        }
        return callWithAsyncErrorHandling(
          source,
          instance,
          3 /* WATCH_CALLBACK */,
          [onCleanup]
        )
      }
    }

    // 5. 其他情况
  } else {
    getter = NOOP
    process.env.NODE_ENV !== 'production' && warnInvalidSource(source)
  }

  /* ....其他逻辑处理 */
}

验证 3.3:
var a = ref(1)
var b = reactive({ name: 'bbbb' })
var c = reactive({ name: 'cccc' })
var d = reactive({ name: 'dddd', age: 18 })

watch(
    () => [d.name, d.age],
    function (newVal, oldVal) {
        console.log(newVal, oldVal)
    }
)

/* 
    [
        "12",
        {
            "name":"bbbb"
        },
        "cccc",
        [
            "dddd",
            18
        ]
    ]

    [
        1,
        {
            "name":"bbbb"
        },
        "cccc",
        [
            "dddd",
            18
        ]
    ]
*/

第一个参数,支持已数组的形式,传递不同类型的参数。

watch源码阅读总结:

发现 vue3的 watch源码这里,会对一个参数,进行很多if判断,用来处理传入参数的不同情况;

主要支持:

  1. ref对象;
  2. reactive对象;
  3. 数组;
  4. 函数;
  5. 其他;

总结: vue3 watch的使用:

ref 的数据

  • 单个 ref:正常传递;
  • 多个 ref:传入一个数组,callback共用,newVal和oldVal也是数组;

reactive 的数据

  • 传入 reactive 对象,默认深度监听,oldVal失效;
  • 传入 reactive 属性:需要以函数返回值的形式返回。默认不开启深度监听,oldVal生效;

支持 传入一个数组,数组中多个函数,函数分别返回 reactive 属性; 支持 传入一个函数,函数返回一个数组,数组中包含多个reactive 属性; 不支持 直接传入 reactive 属性

多个数据

支持已数组的形式,传递不同类型的参数

其他

watchEffect()

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

怎么理解:

  • 传入的一个函数,当依赖项变化的时候,重新执行改函数。
  • watchEffect会在初始化的时候调用一次,与watch的immediate类似

示例代码:

 var a = ref('')
 watchEffect(() => {
 /* 注意一下 ref需要用 .value去触发 */
 console.log('a属性修改啦', a.value)
 })
 // a属性修改啦

a.value = a.value + '改变'
// a属性修改啦 改变

参数:

    var a = ref('')

    watchEffect((onInvalidate) => {
      // 代码一
      console.log('执行一些代码', a.value)
      console.log('执行更多的代码')
      // 代码二
      onInvalidate(() => {
        console.log(
          '除了在初始运行时不被调用,我总是在【执行一些代码】之前被执行(调用)'
        )
      })
    })

    /* 
       执行一些代码
       执行更多的代码
    */

    a.value = a.value + '改变'

    /* 
       除了在初始运行时不被调用,我总是在【执行一些代码】之前被执行(调用)
       执行一些代码
       执行更多的代码
    */

停止监听:

const stop = watchEffect(() => {})

// 当不再需要此侦听器时:
stop()

对比 watch 和 computed,watchEffect:

  • watch 关注监听谁,做什么;

  • computed 用到谁监听谁,注重返回结果;

  • watchEffect 用到谁监听谁,注重过程;

end

  • 加油啦