В предыдущей статье подробно описаноПринцип соответствующей формулы 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, мы поделимся им с вами в следующей главе событий.