💥 preface

Since I have been using Vue to move bricks, I think I have become a skilled Api call engineer. A few days ago, I saw a problem related to Watch and WatchEffect, which taught me a good lesson. 😅

Look at the following code and ask: why can Watch listen, but not WatchEffect ❓

let test = ref({});
watchEffect(() = > {
  console.log(test.value);
});
watch(
  () = > test.value,
  (newVal) = > {
    console.log(newVal);
  },
  {
    deep: true,});const testChange = () = > {
  test.value.aa = 1;
};
Proxy {aa: 1}
Copy the code

Tipes: You guys can stop and think about it, and then look down with your own thoughts

⚙ ️The reason

The WatchEffect can’t listen because Vue is optimized to not blindly detect internal data, and watch should be the first parameter to receive objects and arrays, so it can listen.

Obviously this ambiguous answer did not convince me, so I went to the spring brother asked, spring brother gave me this answer: In the above code, test.value contains only a pointer to an object. In the following method, it changes the contents of the memory address. The pointer does not actually change.

Tipes: Spring brother dig gold address: touch fish spring brother, pay attention to spring brother can not only solve technical problems, there are wonderful stories can see oh 🤩

I looked at this is not a reference to the data type of the problem, why did not think of their own? The reason must be that the above answer is misleading to me, definitely not because I like 😂, and then I was distracted when I typed out such a code, the result found that it can also line 🤔️?

watch(test.value, (newVal) = > {
  console.log(newVal);
});
Copy the code

Tipes: Js data types are divided into basic data types and reference data types. Basic data types are stored in stack memory, and reference data types are stored in two parts: Pointers, which are stored in stack memory, and content, which is stored in heap memory. Pointers point to the memory address of the content. This also raises the issue of depth copy. For the depth of the copy is not thorough partners can look at these two articles:

StructuredClone is a long copy function that can replace JSON or Lodash.

In order to avoid such problems in the future, so we go together to tear the source code, figure out how he is implemented.

🔧 parsing

If you exclude the reactive implementation, just Watch, WatchEffect is relatively simple:

export function watchEffect(effect: WatchEffect, options? : WatchOptionsBase) :WatchStopHandle {
  return doWatch(effect, null, options)
}

/ /... Omit the watchEffect epithet

/ /... Omit the watch overload

// implementation
export function watch<T = any.Immediate extends Readonly<boolean> = false> (source: T | WatchSource
       
        , cb: any, options? : WatchOptions
        
       ) :WatchStopHandle {
  if(__DEV__ && ! isFunction(cb)) {/ /... Omit the error message
  }
  return doWatch(source as any, cb, options)
}
Copy the code

WatchEffect and Watch share the same doWatch function. Let’s take a look at how doWatch works with the above problems:

/ / simplified version
function doWatch(
  source: WatchSource | WatchSource[] | WatchEffect | object,
  cb: WatchCallback | null,
  { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
) :WatchStopHandle {

  / /... Omit error handling

  const instance = currentInstance
  let getter: () = > any
  let forceTrigger = false
  let isMultiSource = false

  if (isRef(source)) { // Determine if it is a ref object
    getter = () = >Value forceTrigger = isShallow(source)}else if (isReactive(source)) { // Check if it is a reactive object
    getter = () = > source
    deep = true // key !!!!!!!!!!
  } else if (isArray(source)) {
    isMultiSource = true
    forceTrigger = source.some(isReactive)
    getter = () = >
      source.map(s= > {
        if (isRef(s)) {
          return s.value
        } else if (isReactive(s)) {
          return traverse(s)
        } else if (isFunction(s)) {
          return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
        } else {
          __DEV__ && warnInvalidSource(s)
        }
      })
  } else if (isFunction(source)) {
    if (cb) { // The second argument has the value Watch
      // getter with cb
      getter = () = >
        callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
    } else { // No value is WatchEffect
      // no cb -> simple effect
      getter = () = > {
        if (instance && instance.isUnmounted) {
          return
        }
        if (cleanup) { // If the function exists, clear it first
          cleanup()
        }
        return callWithAsyncErrorHandling(
          source,
          instance,
          ErrorCodes.WATCH_CALLBACK,
          [onCleanup]
        )
      }
    }
  } else {
    getter = NOOP
    __DEV__ && warnInvalidSource(source)
  }

  // omit backward compatibility

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


  // omit SSR operation
  // omit setting clear listening
  
Copy the code

📑 conclusion

Take a look at the simplified code and see if you can get rid of the responsive implementation. Watch and WatchEffect are very simple. 😄 to see the above line of code is how to achieve, the key is in the following step:

if (isReactive(source)) { // Check if it is a reactive object
    getter = () = > source
    deep = true // key !!!!!!!!!!
  } 
Copy the code

When a reactive object is detected, Vue will set deep to true to enable deep listening. Therefore, the first parameter of Watch will also be listened in accordance with the code written above. [reactive] [reactive] [reactive] [reactive] [reactive] [reactive] [reactive] [reactive] [reactive] [reactive] [reactive] Where Ref and Reactive are available:

Ref source address Reactive source address

If ref is an object, reactive’s proxy is used, otherwise RefImpl’s set and get methods are used.

const convert = <T extends unknown>(val: T): T => isObject(val) ? reactive(val) : val
Copy the code

📋 finishing

Generally speaking, just watching the implementation of Watch and WatchEffect is still sprinkling water for everyone. In terms of interviews, however, Computed data often appears with Watch. When the dependency variable changes, watcher, which calculates attributes, changes dirty to true, indicating that the data is dirty, but it does not evaluate immediately. When obtaining the content of the calculated attribute, “this. Dep.subs. length determines whether there are subscribers”, it will determine the value of dirty. If it is true, it will re-evaluate. If false returns the cached value.

So if you have time, you can look at Computed implementations, and then you can look at responsive implementations, and beat the interviewer.

Remember to help me a little praise oh, finally, I wish you study progress, successful career! 🎆 🎆

I am the front end rookie who study hard in summer twenty-nine. You can find me in the public number twenty-nine front end camp. We can share information with each other and study together! Refueling XDM 💪

Tipes: Previous Vue series interviewers asked what NextTick was looking for. Why did “vue3 series” use Teleport to encapsulate a frame component? Why did “Vue3 series” use Proxy instead of Object.defineProperty?