preface

Take a look at the vue3 code below, listening for data in three different ways

const user = reactive({ name: 'tom' })
✅ Method one, pass the response object
watch(user, (value) = > {
    console.log(value) {name: 'jake'}
})
// ❌ Method two: Pass properties under the response object
watch(user.name, (value) = > {
    console.log(value) // No output
})
// ✅ Method three: pass functions that return attributes of the response object
watch(() = > user.name, (value) = > {
    console.log(value); // Listen successfully, output Jake
})
// Reassign the response object
user.name = 'jake'
Copy the code

Method 2 does not execute the callback, but method 3 uses the function wrap to listen successfully. Why do we need the function wrap pass to listen on object properties? . I will analyze the Vue3 source code based on this question and find out the answer.

Source code analysis

User is an object and user.name is a value type. So why is it that it’s wrapped in a function and can listen, and what’s going on inside?

To test the hypothesis, we started analyzing the watch source code

Start at the entrance

First look at packages/ Runtime-core/SRC/apiwatch.ts file

If we pass user.name it goes into the else logic block, and the getter sets the empty function, which seems to explain why we can’t pass the value type directly, because the watch function doesn’t handle that type at all.

Further analysis: Why does Vue ignore this type

Moving on to line 358, the getter is finally provided to ReactiveEffect to create an effect instance

const effect = new ReactiveEffect(getter, scheduler)
Copy the code

At this point, the core logic comes to the effect.ts file. To understand this file, you need to understand the Vue3 principle of responsiveness.

Vue3 implements data response based on Proxy. The getter of Proxy is used to trigger track function, which adds dependent effect to DEPS. Reassign data, trigger the setter of proxy and trigger the trigger function to execute all the dependent queues. The task to be executed is effect. For example, reassign a response data and the page will update automatically, which is an effect task.

For more on the principle of responsiveness, see the code for a small response system implemented at the end of this article.

Going back to the watch problem, pass user.name directly and effect will become

/ / watch writing
watch(user.name, cb)
/ / effect code
effect(() = > 'tom');
Copy the code

If an effect is a non-proxy object, the getter can’t be triggered, the effect can’t be collected, and the watch will fail.

If the watch passed in is a function, the user.name in Effect triggers the name attribute of the user Proxy, so effect can be added to the dependency.

/ / watch writing
watch(() = > user.name, cb);
/ / effect code
effect(() = > user.name)
Copy the code

So why does Vue ignore value types because Vue relies on Proxy objects for dependency collection

conclusion

This may be difficult to understand because it requires knowledge of the Vue response principle.

Simply put, when using Watch, the first argument can be passed either directly to the responsive object or to the function containing it.

This limitation is due to the fact that the Vue response system needs to collect dependencies using the Proxy getter, and the trace will fail if the non-proxy type is passed.

The articles

“Stop writing VUE3 with vuE2 thinking”

Why vue3 Watch () can’t Listen on Object properties

Hand-grips: Building Enterprise Vue-Hooks

Hand-holding Series: Building Enterprise Vue-Components

Appendix Small response system implementation

// targetMap is used to record different objects
const targetMap = new WeakMap(a)let activeEffect = null

function effect(eff) {
  activeEffect = eff
  activeEffect()
  activeEffect = null
}

// Collect dependencies
function track(target, key) {
  if (activeEffect) {
    let depsMap = targetMap.get(target)
    if(! depsMap) { depsMap =new Map()
      targetMap.set(target, depsMap)
    }

    let dep = depsMap.get(key)
    if(! dep) { dep =new Set()
      depsMap.set(key, dep)
    }
    dep.add(activeEffect)
  }
}

// Trigger dependencies
function trigger(target, key) {
  let depsMap = targetMap.get(target)
  if(! depsMap)return
  let dep = depsMap.get(key)
  if (dep) {
    dep.forEach((effect) = > effect())
  }
}

// Define response data
function reactive(target) {
  const handler = {
    get(target, key, receiver) {
      let result = Reflect.get(target, key, receiver)
      track(target, key)
      return result
    },
    set(target, key, value, receiver) {
      let oldValue = target[key]
      let result = Reflect.set(target, key, value, receiver)
      if(result && oldValue ! = value) { trigger(target, key) }return result
    },
  }
  return new Proxy(target, handler)
}

function watch(source, cb) {
  effect(() = > {
    const res = source();
    cb(res)
  })
}

let product = reactive({ price: 5.quantity: 2 })

watch(() = > product.price, (value) = > {
  console.log(value, 'product change')})setTimeout(() = > {
  product.price = 10
}, 1000);
Copy the code