Vue3中watch无法监听的解决办法

来自:网络
时间:2023-05-17
阅读:

下面的代码在对ref实例赋值完之后。既:test.value = { name: 1 },会发现不执行watch里面的回调函数了,这是为什么呢?

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="../../dist/vue.global.js"></script>
</head>
<body>
  <div id="app">
  </div>
  <script>  
    var { createApp, ref, watch, onMounted  } = Vue;
    var app = createApp({
        setup() {
            var test = ref({});
            onMounted(() => {
              test.value = { name: 1 }
            })
            setInterval(() => {
              test.value.name++
            }, 3000)
            watch(test.value, () => {
              debugger
            })
            return {
                test,
            }
        }
    })
    app.mount('#app')
  </script>
  <script>
  </script>
</body>
</html>

1:定义ref类型的响应式var test = ref({}), 然后开始执行watch(test.value, cb)函数

2: 这时候触发了test.value,也就是ref中的get value()方法,该方法会执行trackRefValue(this)

export function trackRefValue(ref: RefBase<any>) {
  if (shouldTrack && activeEffect) {
    ref = toRaw(ref)
    if (__DEV__) {
      trackEffects(ref.dep || (ref.dep = createDep()), {
        target: ref,
        type: TrackOpTypes.GET,
        key: 'value'
      })
    } else {
      trackEffects(ref.dep || (ref.dep = createDep()))
    }
  }
}
  • 就是为当前ref实例,收集依赖,但是发现shouldTrack为false, activeEffect为undefined,所以不执行后面的逻辑

3:触发ref的get value() 方法之后,开始执行watch函数了

3-1: test.value是响应式属性,所以isReactive(source)为true, deep为true 如何包裹一层函数

if (cb && deep) {
  const baseGetter = getter
  getter = () => traverse(baseGetter())
}

4: 然后实例化构造函数const effect = new ReactiveEffect(getter, scheduler), 执行effect.run()

执行getter = () => traverse(baseGetter()) 为test.value里面的属性搜集ReactiveEffect依赖

5: onMounted之后,直接替换test.value的值,触发了set value()方法

5-1:对新设置的值,重新定义proxy响应式属性toReactive(newVal)

并且触发triggerRefValue(this, newVal), 触发依赖的执行

set value(newVal) {
    const useDirectValue =
      this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
    newVal = useDirectValue ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = useDirectValue ? newVal : toReactive(newVal)
      triggerRefValue(this, newVal)
    }
}

但是get value()的时候,没有收集到ReactiveEffect,所以执行triggerRefValue(this, newVal) 的时候,没有执行到watch的回调函数

6:后面执行setInterval对test.value进行赋值的时候,也没有更新watch的回调,因为set value()的时候 重新执行了一次toReactive(newVal) 之前收集的依赖已经失效了

7:解决方法

// 方法一
watch(() => test.value, () => {
  debugger
}, { deep: true })
// 方法二
watch(test, () => {
  debugger
}, { deep: true })
// 方法三, 在test.value赋值之后执行watch
var { createApp, ref, watch, onMounted  } = Vue;
    var app = createApp({
        setup() {
            var test = ref({});
            onMounted(() => {
              test.value = { name: 1 }
              watch(test.value, () => {
                debugger
              })
            })
            setInterval(() => {
              test.value.name++
            }, 3000)
            // watch(test.value, () => {
            //   debugger
            // })
            return {
                test,
            }
        }
    })
app.mount('#app')

这个写法,不会一开始就触发ref实例的get value()方法, 而是在创建 const effect = new ReactiveEffect(getter, scheduler) ,执行effect.run() 的时候,触发get value()方法,搜集依赖

7-1:当对test.value = {name: 1}赋值的时候,触发set value()方法,就可以触发triggerRefValue(this, newVal) 执行依赖,从而可以再次对新的值,重新搜集依赖

总结

普通的写法进行监听,对ref的值进行赋值,既:test.value = { name: 1 },在get vlaue()的时候,没有收集 到watch的依赖,在触发set value()的时候,就没有再行watch了

而加了函数包裹test.value,在执行effect.run()的时候,才会触发ref的get value(), 从而可以执行trackRefValue(this)收集到依赖

watch(() => test.value, () => {
  debugger
}, { deep: true })
export function trackRefValue(ref: RefBase<any>) {
  if (shouldTrack && activeEffect) {
    ref = toRaw(ref)
    if (__DEV__) {
      trackEffects(ref.dep || (ref.dep = createDep()), {
        target: ref,
        type: TrackOpTypes.GET,
        key: 'value'
      })
    } else {
      trackEffects(ref.dep || (ref.dep = createDep()))
    }
  }
}

再触发set value()的时候,也就可以重新触发effect.run()

返回顶部
顶部