“This is my 28th day of participating in the First Challenge 2022. For details: First Challenge 2022.”

preface

Vue3 Watch document

Watch needs to listen to specific data sources and perform side effects in a separate callback function. By default, it is also lazy — that is, the callback is invoked only when the listening source changes. Watch and Vue3.0 source code learning – Computed in Computed is the API of Vue responsive system, which does some things according to the response of observed objects. This paper mainly studies the source code and execution process of Watch.

Source code analysis

  • findwatchThe position of the function definition\packages\runtime-core\src\apiWatch.ts.watchThere are multiple definition overloads, sowatchYou can use it in a variety of ways. The observation sources can be arrays, single-valued, multi-valued,reactiveObject etc.

  • To observe the sourcesourceThe type can beRef,ComputedRefOr a function that returns any typeT

  • watchFinal definition, accepts two mandatory parameters: the observation sourcesource, the callback functioncb, an optional parameter optionoptions, returns adoWatchThe result of the function is truewatchThe implementation of the
// implementation
export function watch<T = any.Immediate extends Readonly<boolean> = false> (source: T | WatchSource
       
        , cb: any, options? : WatchOptions
        
       ) :WatchStopHandle {...return doWatch(source as any, cb, options)
}
Copy the code
  • dowatchThe following is a look at the main execution flow

  • You first get the current component instance and declare onegetterfunction
  const instance = currentInstance
  let getter: () = > any
Copy the code
  • According to the incomingsourceType, redefinegetterDelta function, you can see thatgetterIt’s going to end up being a function that returns the value that we’re looking at
  if (isRef(source)) { 
    getter = () = > source.value
    forceTrigger = isShallow(source)
  } else if (isReactive(source)) {
    getter = () = > source
    deep = true Default deep is recursive if passed reactive
  } else if (isArray(source)) {
    isMultiSource = true. }else if (isFunction(source)) { // Pass in a function directly
    if (cb) {
      // getter with cb
      getter = () = >
        // To prevent errors, execute cb
        callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
    } else {
      // no cb -> simple effect 
      // No parameter was passed. }}else{... }Copy the code

If deep is set, recursive operations will be performed (recursion inevitably leads to performance degradation, so usually development due to stiffness avoid observing complex types of data objects).

  if (cb && deep) {
    const baseGetter = getter
    getter = () = > traverse(baseGetter()) / / recursion
  }
Copy the code
  • Create a reactive side effect function that will end up at some point in the future whenRelying on the getterWhen changes occur, an asynchronous update operation is performedjob, that is,cbDo it again, as you learned in the previous articleVue3.0 source code learning – update process analysis
  let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
  // Create a scheduled task
  const job: SchedulerJob = () = > {
    if(! effect.active) {return
    }
    if (cb) {
      // watch(source, cb)
      // If cb is set, the side effect function is called immediately,
      // Get the value of the watch reactive variable immediately here
      // So this is the latest value of the reactive data we observed
      const newValue = effect.run()
      if(...// Compare the old and new data differently, i.e. the value has changed
          : hasChanged(newValue, oldValue)) ||
        ...
      ) {
        // cleanup before running cb again
        // Clean up before cb each time
        if (cleanup) {
          cleanup()
        }
        // Call the callback function cb
        callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
          newValue,
          // pass undefined as the old value when it's changed for the first time
          oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
          onCleanup
        ])
        oldValue = newValue
      }
    } else{... }}...let scheduler: EffectScheduler
  // Based on the watchOptions passed by the user, determine how to flush the cb callback
  if (flush === 'sync') {
    // Synchronize execution
    scheduler = job as any // the scheduler function gets called directly
  } else if (flush === 'post') {
    scheduler = () = > queuePostRenderEffect(job, instance && instance.suspense)
  } else {
    // default: 'pre'
    // No default flush
    scheduler = () = > {
      if(! instance || instance.isMounted) {// Queue in the pre update queue, i.e. jump to another update queue
        queuePreFlushCb(job)
      } else{... }}}// Create reactive side effects
  const effect = new ReactiveEffect(getter, scheduler)
Copy the code
  • Determine whether to execute immediatelycb
  // Whether to run cb during initialization
  if (cb) {
    if (immediate) { // Immediate execution is configured
      job() 
    } else {
      oldValue = effect.run() // Save oldValue, equal to execute the getter once}}else if (flush === 'post') {... }else {
    effect.run()
  }
Copy the code
  • Finally returns a cleanup function when not neededwatchExecute this function when
  // returns the cleanup function
  return () = > {
    effect.stop()
    if (instance && instance.scope) {
      // Clean up reactive side effects from component instancesremove(instance.scope.effects! , effect) } }Copy the code

In this way, the source code execution process of watch function is analyzed. In order to have a clearer understanding of this process, further analysis will be made in the form of code debugging in the next chapter.