[Aprendizaje del código fuente de Vue3] API receptiva: watchEffect | El principio de realización del reloj

En Vue2 watches una opción muy común en optionel método de escritura , es muy conveniente monitorear los cambios de una fuente de datos, pero en Vue3 watchse convierte en una API responsiva de manera independiente.

Dirección de la fuente:packages\runtime-core\src\apiWatch.ts

relojEfecto

Dado que muchos de los comportamientos watchen watchEffectson consistentes con, watchEffecten primer lugar , para aplicar y volver a aplicar automáticamente los efectos secundarios en función del estado reactivo, podemos usar watchEffectel método. Inmediatamente ejecuta una función pasada, mientras realiza un seguimiento reactivo de sus dependencias y vuelve a ejecutar la función cuando cambia.

watchEffectLa implementación de la función es muy concisa:

// 首先来看参数类型
export type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void

export interface WatchOptionsBase extends DebuggerOptions {
    
    
  flush?: 'pre' | 'post' | 'sync'
}

export type WatchStopHandle = () => void

export function watchEffect(
  effect: WatchEffect,  // 接收函数类型的变量,并且在这个函数中会传入 onInvalidate 参数,用以清除副作用
  options?: WatchOptionsBase // 在这个对象中有三个属性,你可以修改 flush 来改变副作用的刷新时机,默认为 pre,当修改为 post 时,就可以在组件更新后触发这个副作用侦听器,改同 sync 会强制同步触发。而 onTrack 和 onTrigger 选项可以用于调试侦听器的行为,并且两个参数只能在开发模式下工作。
): WatchStopHandle {
    
    
  return doWatch(effect, null, options)
}

que DebuggerOptionsse encuentrapackages\reactivity\src\effect.ts

export interface DebuggerOptions {
    
    
  onTrack?: (event: DebuggerEvent) => void   // 追踪时触发
  onTrigger?: (event: DebuggerEvent) => void // 触发回调时触发
}

Después de pasar los parámetros, la función se ejecutará y devolverá el valor de retorno de doWatchla función . Dado que watch apitambién llamará doWatcha la función, veremos la lógica específica de doWatchla función al final. Primero watch apiobserve la implementación de funciones de .

mirar

watchNecesita escuchar una fuente de datos específica y realizar efectos secundarios en la función de devolución de llamada. De forma predeterminada, este oyente es perezoso, es decir, la devolución de llamada solo se ejecuta cuando cambia la fuente que se escucha.

watchEffectEn comparación con , watchexisten las siguientes diferencias:

  • ejecutar los efectos secundarios perezosamente;
  • Sea más específico sobre el estado que debería hacer que el oyente se vuelva a ejecutar;
  • Accede a los valores anterior y actual del estado que se está escuchando.

watchLa función de la función tiene cuatro sobrecargas, y no se realizará ningún análisis específico aquí.Aquí, primero veremos los watchparámetros de implementación, y analizaremos watchel núcleo de la implementación en detalle más adelante.doWatch

// overload: array of multiple sources + cb
export function watch<
  T extends MultiWatchSources,
  Immediate extends Readonly<boolean> = false
>(
  sources: [...T],
  cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
  options?: WatchOptions<Immediate>
): WatchStopHandle

// overload: multiple sources w/ `as const`
// watch([foo, bar] as const, () => {})
// somehow [...T] breaks when the type is readonly
export function watch<
  T extends Readonly<MultiWatchSources>,
  Immediate extends Readonly<boolean> = false
>(
  source: T,
  cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
  options?: WatchOptions<Immediate>
): WatchStopHandle

// overload: single source + cb
export function watch<T, Immediate extends Readonly<boolean> = false>(
  source: WatchSource<T>,
  cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
  options?: WatchOptions<Immediate>
): WatchStopHandle

// overload: watching reactive object w/ cb
export function watch<
  T extends object,
  Immediate extends Readonly<boolean> = false
>(
  source: T,
  cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
  options?: WatchOptions<Immediate>
): WatchStopHandle

// implementation
export function watch<T = any, Immediate extends Readonly<boolean> = false>(
  source: T | WatchSource<T>,
  cb: any,
  options?: WatchOptions<Immediate>
): WatchStopHandle {
    
    
  if (__DEV__ && !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.`
    )
  }
  return doWatch(source as any, cb, options)
}

watch recibe 3 parámetros, source es la fuente de datos para escuchar, cb es la función de devolución de llamada y options es la opción de escucha.

parámetro de origen

sourceLos tipos son los siguientes:

export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
type MultiWatchSources = (WatchSource<unknown> | object)[]

Como se puede ver en las dos definiciones de tipo, la fuente de datos admite pasar un solo objeto receptivo o pasar una función que devuelve el mismo tipo genérico y admite pasar una matriz para que se puedan monitorear múltiples fuentes de datos Refal mismo tiempo. Mismo tiempo.Computedsource

parámetro cb

En esta declaración más general, cbel tipo es any, pero cbesta función de devolución de llamada también tiene tipo:

export type WatchCallback<V = any, OV = any> = (
  value: V,
  oldValue: OV,
  onInvalidate: InvalidateCbRegistrator
) => any

En la función de devolución de llamada, se proporcionan las funciones más nuevas value, antiguas yvalue para eliminar los efectos secundarios.onInvalidate

opciones

export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
    
    
  immediate?: Immediate
  deep?: boolean
}

Puede ver optionsque el tipo de WatchOptionses heredado WatchOptionsBase.

Después de analizar los parámetros, se puede ver que la lógica en el cuerpo de la función es watchEffectcasi la misma que la de , pero hay más para detectar si la función de devolución de llamada es un tipo de función en el entorno de desarrollo.Si la función de devolución de llamada no es un función, se emitirá una alarma. doWatchEn comparación con la participación del pase durante la ejecución watchEffect, hay una función de devolución de llamada de segundo parámetro.

doWatch

`watchEffect, watch o la opción watch en el componente eventualmente llamará a la lógica en doWatch cuando se ejecute.

Comencemos con la firma de función doWatchde , que es watchconsistente con .

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

doWatchLa función se divide principalmente en las siguientes partes:

  1. Fuente estandarizada , ensamblada en una función getter
  2. Montar la función de trabajo. Determine si el valor escuchado ha cambiado y, si hay un cambio, ejecute la función getter y la devolución de llamada cb
  3. Ensamble la función del programador, el programador es responsable de llamar a la función de trabajo en el momento adecuado (de acuerdo con options.flush , es decir, el momento de la actualización del efecto secundario), que se ejecuta antes de que el componente se actualice de manera predeterminada
  4. activar la escucha
  5. volver dejar de escuchar la función
function doWatch(
  source: WatchSource | WatchSource[] | WatchEffect | object,
  cb: WatchCallback | null,
  {
    
     immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): WatchStopHandle {
    
    
 
  // 1. 根据 source 的类型组装 getter    
  let getter: () => any 
  if (isRef(source)) {
    
    
    getter = ...
  } else if (isReactive(source)) {
    
    
    getter = ...
  } else if (isArray(source)) {
    
    
    isMultiSource = true
    forceTrigger = source.some(isReactive) 
     getter = ...
  } else if (isFunction(source)) {
    
    
    if (cb) {
    
    
      getter = ...
    } else {
    
    
      getter = ...
    }
  } else {
    
    
    getter = ...
    __DEV__ && warnInvalidSource(source)
  }

  // 2. 组装 job
  const job: SchedulerJob = () => {
    
    
	// ...
  }

  // 3. 组装 scheduler
  let scheduler: EffectScheduler = ...

  // 4. 开启侦听,侦听的是 getter 函数
  const effect = new ReactiveEffect(getter, scheduler)
  effect.run()

  // 5. 返回停止侦听函数
  return () => {
    
    
    effect.stop()
    if (instance && instance.scope) {
    
    
      remove(instance.scope.effects!, effect)
    }
  }
}

1. Estandarizar la fuente y ensamblarla en una función getter

Determine el tipo de fuente, los diferentes tipos de fuente, el empaquetado estandarizado en la función getter:

  • árbitro:() => source.value
  • reactivo:() => traverse(source)
  • Matriz: de acuerdo con el tipo de subelemento, envuélvalo en una función getter
  • Función: callWithErrorHandlingEnvuelto con , de hecho, llama directamente a la función fuente

El código y los comentarios relevantes son los siguientes:

  const instance = currentInstance // 取当前组件实例
  let getter: () => any            // getter 最终会当做副作用的函数参数传入
  let forceTrigger = false         // 标识是否需要强制更新
  let isMultiSource = false        // 标记传入的是单个数据源还是以数组形式传入的多个数据源。

  // 1. ref 类型 
  if (isRef(source)) {
    
    
    getter = () => source.value      // 直接解包取source.value 值,
    forceTrigger = !!source._shallow // 标记会根据是否是shallowRef设置
  // 2. reactive 类型
  } else if (isReactive(source)) {
    
    
    getter = () => source  // 直接返回 source,因为 reactive 的值不需要解包获取
    deep = true            // 由于 reactive 中往往有多个属性,所以将deep设置为 true。这里可以看出从外部给 reactive 设置 deep 是无效的
  // 3. 数组 array 类型
  } else if (isArray(source)) {
    
    
    isMultiSource = true
    forceTrigger = source.some(isReactive) // 会根据数组中是否存在 reactive 响应式对象来设置
    // 数组形式,由source内各个元素的单个 getter 结果
    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)
        }
      })
   // 4. source 是函数 function 类型
  } else if (isFunction(source)) {
    
    
    // 4.1 如果有回调函数
    if (cb) {
    
    
      // getter with cb
      // getter 就是 source 函数执行的结果,这种情况一般是 watch api 中的数据源以函数的形式传入。
      // callWithErrorHandling 中做了一些 vue 错误信息的统一处理,有更好的错误提示
      getter = () =>
        callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
    } else {
    
    
      // no cb -> simple effect
      // 4.2 如果没有回调函数,那么此时就是 watchEffect api 的场景了
      getter = () => {
    
    
        // 如果组件实例已经卸载,则不执行,直接返回
        if (instance && instance.isUnmounted) {
    
    
          return
        }
        // cleanup不为void时 清除依赖
        if (cleanup) {
    
    
          cleanup()
        }
        // 执行source函数
        return callWithAsyncErrorHandling(
          source,
          instance,
          ErrorCodes.WATCH_CALLBACK,
          [onInvalidate]
        )
      }
    }
  // 5. 其余情况 将 getter 设置为空函数,并且报出 source 不合法的警告
  } else {
    
    
    getter = NOOP
    __DEV__ && warnInvalidSource(source)
  }

A continuación, se procesará la escena watchen Cuando haya una devolución de llamada y deepla opción truesea , la función se envolverá traversecon getterpara escuchar cada propiedad en la fuente de datos atravesando recursivamente.

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

atravesar

El papel de atravesar: para reactiveobjetos o parámetros de configuración deep, es necesario escuchar cambios profundos , lo que requiere un recorrido profundo de todo el objeto, acceso profundo a todas sus variables de respuesta y colección de dependencias.

// 深度遍历对象,只是访问响应式变量,不做任何处理
// 访问就会触发响应式变量的 getter,从而触发依赖收集
export function traverse(value: unknown, seen?: Set<unknown>) {
    
    
  if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
    
    
    return value
  }
  seen = seen || new Set()
  if (seen.has(value)) {
    
    
    return value
  }
  seen.add(value)
  if (isRef(value)) {
    
    
    traverse(value.value, seen)
  } else if (isArray(value)) {
    
    
    // 继续深入遍历数组
    for (let i = 0; i < value.length; i++) {
    
    
      traverse(value[i], seen)
    }
  } else if (isSet(value) || isMap(value)) {
    
    
    value.forEach((v: any) => {
    
    
      traverse(v, seen)
    })
  } else if (isPlainObject(value)) {
    
    
    // 是对象则继续深入遍历
    for (const key in value) {
    
    
      traverse((value as any)[key], seen)
    }
  }
  return value
}

Después de eso, se declararán cleanuplas funciones y e, y se le asignará un valor a la función durante la ejecución de la función. Cuando la función de efectos secundarios ejecuta algunos efectos secundarios asincrónicos, estas respuestas deben borrarse cuando falla, por lo que la función que escucha la entrada del efecto secundario puede recibir una función como parámetro de entrada , que se utiliza para registrar la devolución de llamada cuando falla la limpieza. Esta devolución de llamada de invalidación se activará cuando:onInvalidatonInvalidatecleanuponInvalidate

  • Cuando el efecto secundario está a punto de volver a ejecutarse.
  • El oyente se detiene ( setup()en watchEffectel desmontaje del componente, si se usa en un enlace de ciclo de vida).
let cleanup: () => void
let onInvalidate: InvalidateCbRegistrator = (fn: () => void) => {
    
    
  cleanup = runner.options.onStop = () => {
    
    
    callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
  }
}

2. Montar el trabajo

La función Trabajo se llama directa o indirectamente en la función del planificador.

Declare una función de trabajo, que eventualmente se pasará como una función de devolución de llamada en el programador, porque es un cierre que depende de muchas variables en el ámbito externo.

Dependiendo de si hay una función de devolución de llamada, jobconfigure allowRecursela propiedad de . Esta configuración es muy importante para hacer que el trabajo sea una devolución de llamada para un observador, de modo que el programador sepa que puede llamarse a sí mismo.

  // 初始化 oldValue
  let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
  // 声明一个 job 调度器任务
  const job: SchedulerJob = () => {
    
    
    if (!effect.active) {
    
     // 如果副作用以停用则直接返回
      return
    }
    if (cb) {
    
    
      // watch(source, cb)
      //  在 scheduler 中需要手动直接执行 effect.run,这里会执行 getter 函数
      // 先执行 getter 获取返回值,如果返回值变化,才执行 cb。
      const newValue = effect.run()
      
      // 判断是否需要执行 cb
      // 1. getter 函数的值被改变,没有发生改变则不执行 cb 回调
      // 2. 设置了 deep 深度监听
      // 3. forceTrigger 为 true
      if (
        deep ||
        forceTrigger ||
        (isMultiSource
          ? (newValue as any[]).some((v, i) =>
              hasChanged(v, (oldValue as any[])[i])
            )
          : hasChanged(newValue, oldValue)) ||
        (__COMPAT__ &&
          isArray(newValue) &&
          isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance))
      ) {
    
    
        // cleanup before running cb again
        // 当回调再次执行前先清除副作用
        if (cleanup) {
    
    
          cleanup()
        }
        // 触发 watch api 的回调,并将 newValue、oldValue、onInvalidate 传入
        callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
          newValue,
          // pass undefined as the old value when it's changed for the first time
          // 首次调用时,将 oldValue 的值设置为 undefined
          oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
          onInvalidate
        ])
        oldValue = newValue // 触发回调后,更新 oldValue
      }
    } else {
    
    
      // watchEffect的场景,直接执行 runner
      effect.run()
    }
  }
  // important: mark the job as a watcher callback so that scheduler knows
// 重要:让调度器任务作为侦听器的回调以至于调度器能知道它可以被允许自己派发更新
// it is allowed to self-trigger (#1727)
job.allowRecurse = !!cb

3. Montar el programador

getterLa función se ejecuta cuando cambia una variable reactiva escuchada scheduler.

Declare schedulerun objeto de programador y flushdetermine el tiempo de ejecución del programador de acuerdo con los parámetros pasados. El código fuente y los comentarios de esta parte de la lógica son los siguientes:


let scheduler: ReactiveEffectOptions['scheduler'] // 声明一个调度器
if (flush === 'sync') {
    
     // 同步
  scheduler = job as any // 这个调度器函数会立即被执行
} else if (flush === 'post') {
    
     // 延迟
  // 调度器会将任务推入一个延迟执行的队列中,在组件被挂载后、更新的生命周期中执行。
  scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
    
    
    // 默认情况 'pre'
  scheduler = () => {
    
    
    // 区分组件是否已经挂载  
    if (!instance || instance.isMounted) {
    
    
      queuePreFlushCb(job) // 组件挂载后则会被推入优先执行时机的队列中
    } else {
    
    
      // 在 pre 选型中,第一次调用必须发生在组件挂载之前
      // 所以这次调用是同步的
      job()
    }
  }
}

4. Activa la escucha

Después de procesar la parte anterior del programador, const effect = new ReactiveEffect(getter, scheduler)se crean objetos y se ejecutan funciones de efectos secundarios por primera vez. Aquí getter, la función para la recopilación de dependencias.

Si hay un cambio en las dependencias, ejecute schedulerla función :

  • Si watchhay una función de devolución de llamada
    • watchSi immediatela opción está configurada, jobla tarea del programador .
    • De lo contrario, effect.run()el efecto secundario y el valor devuelto se asigna a oldValue.
  • flushSi el tiempo de actualización de es post, effect.run()colóquelo en la cola de tiempo de retraso y espere a que el componente se monte antes de la ejecución.
  • El resto de los casos están ejecutando directamente effect.run()efectos secundarios por primera vez.
  const effect = new ReactiveEffect(getter, scheduler)

  if (__DEV__) {
    
    
    effect.onTrack = onTrack
    effect.onTrigger = onTrigger
  }

  // initial run
  if (cb) {
    
    
    if (immediate) {
    
    
      job()  // 有回调函数且是 imeediate 选项的立即执行调度器任务
    } else {
    
    
      oldValue = effect.run() // 否则执行一次effect.run(),并将返回值赋值给 oldValue
    }
  } else if (flush === 'post') {
    
    
    // 如果调用时机为 post,则推入延迟执行队列
    queuePostRenderEffect(
      effect.run.bind(effect),
      instance && instance.suspense
    )
  } else {
    
    
    // 其余情况立即首次执行副作用
    effect.run()
  }

5. Volver a dejar de escuchar la función

Finalmente, doWatchla función devolverá una función. La función de esta función es dejar de escuchar, por lo que cuando la use watch, puede watchEffectllamar explícitamente al valor de retorno para dejar de escuchar.

// 返回一个函数,用以显式的结束侦听
return () => {
    
    
    effect.stop()
    // 移除当前组件上的对应的 effect
    if (instance && instance.scope) {
    
    
      remove(instance.scope.effects!, effect)
    }
}

doWatchToda la función ha sido ejecutada aquí, y ahora todas las variables han sido declaradas.

epílogo

Si este artículo es de alguna ayuda para ti, por favor dale me gusta para apoyarlo, gracias

Supongo que te gusta

Origin blog.csdn.net/qq_38987146/article/details/123211407
Recomendado
Clasificación