Просмотр Vue3.0 и вычисленный анализ исходного кода (пример диаграммы)

Vue.js
Просмотр Vue3.0 и вычисленный анализ исходного кода (пример диаграммы)

В предыдущей статье подробно описаноПринцип соответствующей формулы Vue3.0, знать плюсы и минусы использования прокси вместо Object.defineProperty, понимать общий процесс сбора зависимостей и распространения обновлений, а также понимать принцип отзывчивости vue3.0 В этом разделе мы изучим изменения в watch в vue3. 0.

часы и часыЭффект

Как мы объясняли ранее, vue3.0 отменяет концепцию отслеживания рендеринга и заменяет ее хуками побочных эффектов для завершения представления при изменении зависимостей.

 /* 创建一个渲染 effect */
instance.update = effect(function componentEffect() {
    //...省去的内容后面会讲到
},{ scheduler: queueJob })

Далее мы анализируемwatchа такжеwatchEffect

смотреть и смотретьЭффект использования

watchEffect

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

Есть два параметра от watchEffect, первый — эффект функции побочного эффекта, а второй — параметры элемента конфигурации параметра, мы проанализируем использование каждого параметра один за другим.

① Мониторинг зависимостей

import { reactive, watchEffect } from 'vue'

const state = reactive({
  count: 0
})
watchEffect(() => {
   const number = `my age is ${state.count}`
   console.log(number)
})

watchEffect требуется функция fn, которая применяет желаемый побочный эффект. Он немедленно выполняет функцию и отслеживает все свойства реактивного состояния, используемые в качестве зависимостей во время выполнения. Состояние, представленное в этом состоянии, будет отслеживаться как зависимость этого наблюдателя после первоначального выполнения. Когда состояние изменится когда-нибудь в будущем, внутренняя функция выполнится снова.

Мы можем заключить, что 1 Во-первых, функция watchEffect выполняется сразу один раз. 2 Счетчик в состоянии, сгенерированном используемым в нем реактивом, будет отслеживаться как зависимость.При срабатывании набора зависимость изменяется, и функция выполняется снова для достижения цели мониторинга.

②Очистить побочные эффекты

Когда мы выполняем некоторые операции в функции побочных эффектов watchEffect, такие как мониторинг dom или задержка таймера, нам нужно очищать эти побочные эффекты вовремя, когда компонент выгружается, чтобы избежать влияния задержки, нам нужен хук useEffect, как в реакции Функция очистки очистки, тот же vue3.0 также предоставляет аналогичный метод.

watchEffect((onInvalidate)=>{
   const handerClick = ()=>{} 
   document.addEventListener('click',handerClick)
   onInvalidate(()=>{
       /*
        执行时机:  在副作用即将重新执行时,如果在setup()或生命周期钩子函数中使用watchEffect, 则在卸载组件时执行此函数。
       */
       document.removeEventListener('click',handerClick)
    })	
})

③Перестань слушать

Vue3.0 также выполняет функциональную компенсацию для часов 2.0, мы можем вручную отключить эти эффекты мониторинга, когда это необходимо.

Автоматически останавливать прослушивание: когда watchEffect вызывается в функции setup() или обработчике жизненного цикла компонента, прослушиватель будет связан с жизненным циклом компонента и автоматически остановится, когда компонент будет выгружен.

Чтобы остановить прослушивание вручную:

const watcherStop=watchEffect(()=>{})	  	            
watcherStop()

③Асинхронная работа

WatchEffect в vue3.0 не поддерживает асинхронный асинхронный сахар ожидания перед отправкой, такой как useEffect в реакции, и полностью поддерживает асинхронные операции.

 watchEffect(async () => {})

Для второго параметра watchEffect он в основном предоставляет независимые элементы конфигурации для watchEffect. Отладка эффекта мониторинга.

export interface BaseWatchOptions {
  flush?: 'pre' | 'post' | 'sync'
  onTrack?: ReactiveEffectOptions['onTrack']
  onTrigger?: ReactiveEffectOptions['onTrigger']
}

flush

Из исходного кода мы видим, что есть три параметра конфигурации: flush, onTrack и onTrigger.В случаях, когда требуется синхронизация или эффект наблюдателя перезапускается перед обновлением компонента, можно передать объект дополнительных параметров с параметром сброса (по умолчанию — «post»)


watchEffect(
  () => {
  },
  {
    flush: 'sync' // 同步触发
  }
)

watchEffect(
  () => {
  },
  {
    flush: 'pre' // 在组件更新之前触发
  }
)

onTrack и onTrigger

watchEffect(
  () => {
  },
  {  
    onTrigger(e) {  //当依赖项的变化触发watcher回调时,将调用onTrigger
       console.log('依赖项改变,触发set')
    },
    onTrack(e){ //
       console.log('依赖项被调用,触发get) 
    }
  }
)

Как мы видим выше:

onTrackonTrigger вызывается, когда изменение зависимости вызывает обратный вызов наблюдателя.

onTriggeronTrack вызывается, когда свойство с отслеживанием состояния или ссылка вызывается как зависимость.

Поговорив об основном использовании watchEffect, давайте взглянем на использование watch.

watch

watchapi точно эквивалентен 2.x this.$watch (и соответствующим параметрам просмотра). Мониторинг требует мониторинга определенного источника данных и применения побочных эффектов в отдельной функции обратного вызова. Он также ленив по умолчанию, т. е. обратный вызов вызывается только при изменении отслеживаемого источника.

По сравнению с watchEffect, watch позволяет нам:

1 Ленивое выполнение побочных эффектов

2 уточнить, какое состояние должно инициировать повторный запуск наблюдателя;

3 Доступ к предыдущим и текущим значениям контролируемого состояния.

// 监听state
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)
/* 监听一个ref */
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})

Мы можем сделать вывод, что объект прослушивания может быть атрибутом под объектом состояния, сгенерированным реактивным, или может быть атрибутом ref.

часы могут контролировать несколько одновременно.

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})

Принцип часов и часовой эффект

Узнав об использовании watch и watchEffect, давайте посмотрим на принцип watch и watchEffect и перейдем непосредственно к исходному коду, не говоря ерунды.

посмотреть исходный код

export function watch<T = any>(
  source: WatchSource<T> | WatchSource<T>[],  /* getter方法  */
  cb: WatchCallback<T>,                       /* hander回调函数 */
  options?: WatchOptions                      /* watchOptions */
): StopHandle { 
  return doWatch(source, cb, options)
}

watch принимает три параметра.Вышеуказанные три параметра были представлены вам, а именно: метод получения, функция обратного вызова и элемент конфигурации параметров. Далее идет watchEffect

Исходный код watchEffect

export function watchEffect(
  effect: WatchEffect,         /* watch effect */ 
  options?: BaseWatchOptions   /* watchOptions */
): StopHandle {
  return doWatch(effect, null, options)
}

Будь то watch или watchEffect, последняя логикаdoWatchметод, то что именно делает doWatch?

основной метод doWatch

Основной код процесса просмотра выглядит следующим образом

function doWatch(
  source: WatchSource | WatchSource[] | WatchEffect,
  cb: WatchCallback | null,
  { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): StopHandle {
  /* 此时的 instance 是当前正在初始化操作的 instance  */
  const instance = currentInstance
  let getter: () => any
  if (isArray(source)) { /*  判断source 为数组 ,此时是watch情况 */
    getter = () =>
      source.map(
        s =>
          isRef(s)
            ? s.value
            : callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
      )
  /* 判断ref情况 ,此时watch api情况*/
  } else if (isRef(source)) {
    getter = () => source.value
   /* 正常watch情况,处理getter () => state.count */
  } else if (cb) { 
    getter = () =>
      callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
  } else {
    /*  watchEffect 情况 */
    getter = () => {
      if (instance && instance.isUnmounted) {
        return
      }
      if (cleanup) {
        cleanup()
      }
      return callWithErrorHandling(
        source,
        instance,
        ErrorCodes.WATCH_CALLBACK,
        [onInvalidate]
      )
    }
  }
   /* 处理深度监听逻辑 */
  if (cb && deep) {
    const baseGetter = getter
    /* 将当前 */
    getter = () => traverse(baseGetter())
  }

  let cleanup: () => void
  /* 清除当前watchEffect */
  const onInvalidate: InvalidateCbRegistrator = (fn: () => void) => {
    cleanup = runner.options.onStop = () => {
      callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
    }
  }
  
  let oldValue = isArray(source) ? [] : INITIAL_WATCHER_VALUE

  const applyCb = cb
    ? () => {
        if (instance && instance.isUnmounted) {
          return
        }
        const newValue = runner()
        if (deep || hasChanged(newValue, oldValue)) {
          if (cleanup) {
            cleanup()
          }
          callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
            newValue,
            oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
            onInvalidate
          ])
          oldValue = newValue
        }
      }
    : void 0
  /* TODO:  scheduler事件调度*/
  let scheduler: (job: () => any) => void
  if (flush === 'sync') { /* 同步执行 */
    scheduler = invoke
  } else if (flush === 'pre') { /* 在组件更新之前执行 */
    scheduler = job => {
      if (!instance || instance.isMounted) {
        queueJob(job)
      } else {
        job()
      }
    }
  } else {  /* 正常情况 */
    scheduler = job => queuePostRenderEffect(job, instance && instance.suspense)
  }
  const runner = effect(getter, {
    lazy: true, /* 此时 lazy 为true ,当前watchEffect不会立即执行 */
    computed: true,
    onTrack,
    onTrigger,
    scheduler: applyCb ? () => scheduler(applyCb) : scheduler
  })

  recordInstanceBoundEffect(runner)
  /* 执行watcherEffect函数 */
  if (applyCb) {
    if (immediate) {
      applyCb()
    } else {
      oldValue = runner()
    }
  } else {
    runner()
  }
  /* 返回函数 ,用终止当前的watchEffect */
  return () => {
    stop(runner)
    if (instance) {
      remove(instance.effects!, runner)
    }
  }
}

Общая логика watchApi такова:

1 Инкапсулировать метод получения

Во-первых, watch будет формировать геттер-методы в соответствии с разными типами источников.

Зачем использовать геттерный метод? причина проста,Когда функция эффекта выполнения будет сформирована следующей, будет выполнен метод получения, который может прочитать атрибут данных или атрибут ref, обработанный прокси-сервером, запустить перехватчик получения объекта прокси-объекта и собрать зависимости.

2 Сформируйте обратный вызов прослушивателя applyCb

В это время, если это метод doWatch, вызываемый watch в API композиции, будет функция обратного вызова cb, если есть cb, то после следующего выполнения метода getter будет сформировано новое новое значение, а затем функция обратного вызова будет исполняться, т.смотреть функцию прослушивания.

3 обработки эффектов, бегун

Передайте геттер, сформированный на первом этапе, для обработки эффекта, а затем сгенерируйте метод бегуна. Во-первых, метод бегуна в это время представляет собой функцию эффекта, созданную createReactiveEffect, которую можно назвать watcheffect. Действующие зависимости используются для сбора зависимости.смотреть функцию прослушиванияОн передается текущему эффекту через обработку планировщика, а метод-геттер передается текущему эффекту как fn.При изменении зависимостей сначала выполняется fn, метод-геттер.

4 Казнь бегуна

Затем выполните метод раннера Во время выполнения метода раннера будет сделано несколько важных вещей.

Один использует текущий эффект как activeEffect.

2. Выполните геттер-метод для сбора зависимостей, и собранные зависимости будут сохранены в зависимостях текущего эффекта.

3 Deps текущего свойства сохраняет текущий эффект.

5 Отслеживание зависимостей

При изменении зависимостей в deps будет запущен метод установки атрибутов прокси, а затем будет пройден атрибут deps, чтобы определить, есть ли планировщик на текущем эффекте.В процессе обработки часов есть планировщик. Затем будет выполнена логика триггера из заданной логики в предыдущей главе.

 effect.options.scheduler(effect)

И в это времяscheduler, есть два случая

 applyCb ? () => scheduler(applyCb) : scheduler

① Когда мы используем watchEffect в составе API, нет функции обратного вызова applyCb, затем выполняемscheduler(effect), выполнит текущий эффект в расписании, то есть watchEffect.

② Когда мы используем часы в составе API, они будут выполняться в это время.scheduler(applyCb), то текущая функция обратного вызова applyCb (здесь мы можем понять функцию прослушивателя наблюдения) будет передана планировщику для выполнения, а не сам текущий эффект watchEffect.

Пример анализа

Возьмем в качестве примера часы. Давайте используем небольшую демонстрацию, чтобы проанализировать весь процесс часов.

Пример 🌰:

<div id="app">
   <p>{{ count }}</p>
   <button @input="add" >add</button>
</div>

<script>
const { reactive, watch, toRefs } = Vue
Vue.createApp({
  setup(){
    const state = reactive({
       count:1,
    })
    const add = () => state.count++
    watch(state.count,(count, prevCount) => {
       console.log('新的count=' , count )
    })
    return {
      ...toRefs(state),
      add
    }
  }
}).mount('#app')
</script>

Как показано выше, когда мы нажимаем кнопку, вызываетсяstate.count++, функция прослушивания watch listenCb сработает и распечатает новый счетчик, затем мы создадим диаграмму всего процесса в сочетании с этим примером.

Два вычисляемых свойства

В центре внимания часов, упомянутых ранее, находится генерация обновлений данных.отслеживание зависимостей, в то время как компьютер фокусируется накеш данныха такжеОбработка ссылок,ЭтоРазница между часами и компьютером, вычисленные вычислительные свойства, мы вместе проанализировали процесс наблюдения, а затем давайте взглянем на вычисляемый принцип.

рассчитанное использование

Computed принимает функцию получения и возвращает неизменяемый реактивный объект ref для значения, возвращаемого получателем. Во-первых, давайте посмотрим на использование вычисляемых

Использование 1: API композиции

<div id="app">
   <p>{{ plusOne }}</p>
</div>
<script>
const { ref, computed } = Vue
Vue.createApp({
  setup() {
    const count = ref(1)
    const plusOne = computed(() => count.value + 1)
    return {
      plusOne
    }
  }
}).mount('#app')
</script>

Использование 2: vue2.0options

<div id="app">
   <p>{{ plusOne }}</p>
</div>

<script>
Vue.createApp({
  data: () => ({
    number: 1
  }),
  computed: {
    plusOne() {
      return this.number + 1
    }  
  }
}).mount('#app')
</script>

вычисляемый принцип

исходный код компьютера

export function computed<T>(
  options: WritableComputedOptions<T>
): WritableComputedRef<T>
export function computed<T>(
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
) {
  let getter: ComputedGetter<T>
  let setter: ComputedSetter<T>
  if (isFunction(getterOrOptions)) {  /* 处理只有get函数的逻辑 */
    getter = getterOrOptions
    setter = () => {}
  } else { /* 还有 getter 和 setter情况 */
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }
  let dirty = true
  let value: T
  let computed: ComputedRef<T>
  const runner = effect(getter, {
    lazy: true,
    computed: true,
    scheduler: () => {
      if (!dirty) {
        dirty = true /* 派发所有引用当前计算属性的副作用函数effect */
        trigger(computed, TriggerOpTypes.SET, 'value')
      }
    }
  })
  computed = {
    _isRef: true,
    effect: runner,
    get value() { 
      if (dirty) {
        /* 运行computer函数内容 */
        value = runner()
        dirty = false
      }/* 收集引入当前computer属性的依赖 */
      track(computed, TrackOpTypes.GET, 'value')
      return value
    },
    set value(newValue: T) {
      setter(newValue)
    }
  } as any
  return computed
}

Будь то уникальный Composition API vue3.0 или форма параметров vue2.0, вычисляется окончательная логика.Процесс инициализации Composition AP и опций будет рассмотрен в следующих главах.

Суммировать

Три этапа:

① Формируйте вычисленный эффект: сначала определите текущее вычисляемое свойство в соответствии с текущим типом параметра, будь то простой геттер или установщик и геттер, которые могут изменить свойство, и передайте геттер в качестве обратного вызова функции эффекта для формирования эффекта. , который мы называем здесьcomputedEffect, планированиемcomputedEffec. В функции реактивные изменения или изменения ref, на которые ссылается текущий вычисляемый, отслеживаются до отслеживания зависимостей, которое вводит свои собственные вычисляемые свойства, а затем формируется и возвращается вычисляемый объект.

②Сбор зависимостей: когда мы обращаемся к вычисляемому свойству, метод отслеживания будет вызываться для сбора зависимостей, и будет выполняться тот же процесс, что и реактивный.Важным моментом здесь является то, что при сборе зависимостей вычисляемого объекта бегун( ) будет вызван метод. , runner() выполняет метод геттера, а затем собирает реактивные или ref-зависимости текущей вычисляемой ссылки, то есть почему текущая функция геттера будет выполняться, когда зависимости в вычисляемом обновляются до формы новое значение

③ Обновление отправки: при обновлении зависимостей reactive или ref будет запущен набор, а затем будет запущено выполнение функции бегуна.Выполнение функции бегуна будет пересчитывать новое значение, выполнение функции бегуна будет выполнить функцию планировщика, и планировщик выполнит текущую вычисляемую функцию.Зависимости вычисляемых свойств, отслеживание всех зависимостей, которые ссылаются на текущий компьютер, и обновление нового значения

Пример 🌰:

<div id="app">
   <p>{{ plusOne }}</p>
   <button @input="add" >add</button>
</div>

<script>
Vue.createApp({
  data: () => ({
    number: 1
  }),
  computed: {
    plusOne() {
      return this.number + 1
    }  
  },
  methods: {
    add(){
      this.number++
    }
  }
}).mount('#app')
</script>

В сочетании с компьютерным процессом блок-схема, сформированная приведенным выше примером, выглядит следующим образом.

В приведенном выше примере, когда нажимается метод кнопки добавления, будет запущен метод set числовой зависимости, а затем будет вызван текущий plusOne для генерацииComputedEffect (функция запуска в исходном коде), то он сам выполнит plusOne, сгенерирует новое значение и затем вызовет обратный вызовtrigger, и выполнить диспетчеризацию, вычисленную в свою очередь, для создания обновлений зависимостей -> заменить

{{ plusOne }}

плюс один в .

утверждение

Говоря о процессе просмотра и компьютерном процессе, концепция планировщика будет вводиться много раз.Что касается планирования событий vue3.0, мы поделимся им с вами в следующей главе событий.