В этой статье мы подробно рассмотрим, что такое watchers
и как их можно использовать в Vue js 3 с помощью Composition API и Script Setup. Обычно я люблю рассматривать оба API в одном посте, но в данном случае существует слишком много различий, которые усложнят статью, поэтому я решил разделить ее на две отдельные статьи. Вы можете найти API Options в этом посте: Как использовать часы в Vue 3 в Option API.
В этой статье я собираюсь предоставить как можно больше деталей, чтобы сделать ее простой для понимания, но базовое понимание Vue Js и его жизненного цикла будет полезным.
- Что такое наблюдатели в Vue Js
- Наблюдатели и жизненные циклы Vue Js
- Что вызывает вызов часов
- Когда срабатывает watch
- Примеры из реальной жизни
- Наблюдение за несколькими переменными и встроенный геттер
- Реактивные объекты Watch — AKA DEEP
- Немедленный триггер — он же немедленный
- Непосредственное наблюдение
- WatchEffect
- Запуск после манипуляций с DOM — AKA Flush
- Резюме
Что такое наблюдатели в Vue Js
Прежде чем мы научимся использовать watch
Vue Js, мы должны сначала определить, что это за опция и когда мы должны ее использовать.
Наблюдатели предоставляют пользователям Vue Js возможность производить побочный эффект в ответ на изменение состояния одной или нескольких реактивных переменных.
Наблюдатели очень похожи на вычисляемые свойства, поскольку оба они определяются как функция, позволяющая пользователю «следить» за изменением свойства или данных. Несмотря на то, что обычно новые разработчики Vue путаются между этими двумя опциями, между ними существует четкое различие.
Вычисляемые свойства возвращают значение и не производят никаких побочных эффектов. Так, например, полное имя может быть вычисляемым свойством или сумма имеющихся строк может быть вычисляемым свойством. Вычисляемые свойства не должны делать ничего другого, кроме как создавать производные значения, и никогда не вызывать никаких других действий внутри них.
Наблюдатели, с другой стороны, специально предназначены для создания побочных эффектов. Например, запись журналов при изменении выбора пользователем или вызов API при выполнении определенного условия. Это идеальный пример watchers
, поскольку они не возвращают никакого значения, а просто запускают действие как следствие изменения одного или нескольких реактивных свойств.
Наблюдатели не очень распространены, и вы, вероятно, будете использовать их только в особых случаях, но они являются чрезвычайно полезной функцией для сложных компонентов, которые полагаются на побочные эффекты (журналы, вызовы API, оценка из набора данных).
Наблюдатели и жизненные циклы Vue Js
Прежде чем мы перейдем к обсуждению того, как использовать эту функцию, важно понять, когда эта функция происходит и когда она срабатывает. Понимание ее расположения в жизненном цикле Vue не только будет полезно для ее использования, но и поможет вам понять расширенные сценарии использования.
Чтобы полностью понять опцию watch
, нам нужно узнать, «что» вызывает ее, и «когда» срабатывает метод.
Что вызывает вызов часов
Как мы уже говорили ранее, watch
вызывается «изменением состояния». Это означает, что часы, как и вычисления, напрямую связаны с одной или несколькими переменными (data, props, computed и даже геттеры Vuex).
Когда переменная, на которую смотрит watcher, изменится, будет вызван назначенный метод. Прежде чем мы перейдем к попытке понять, когда это происходит в жизненном цикле Vue, мы рассмотрим пару простых примеров, чтобы прояснить вышеприведенный абзац.
Если вы хоть раз использовали Vue Js, вы прекрасно знаете, что вычисляемое свойство будет переоцениваться, как только изменится что-либо, входящее в его блок методов.
<script setup>
import { ref, computed } from 'vue'
const username = ref('Zelig880');
const myUsername = computed(() => {
return `My username is: ${this.username}`;
})
</script>
В приведенном выше примере вычисляемое свойство myUsername
сработает, как только изменится ссылка username
. Таким образом, если в вычисляемом методе любая реактивная ссылка, используемая в его теле, будет наблюдаться, то в методе watch все работает по-другому, поскольку «наблюдаемая» переменная(ы) должна быть объявлена как часть аргумента функции, как показано ниже:
watch(question, async (newQuestion, oldQuestion) => {
<script setup>
import { ref, computed } from 'vue'
const username = ref('Zelig880');
watch(username, (newUsername) => {
// Do something with the updated value.
});
</script>
В приведенном выше примере метод watch будет запущен при изменении ссылок на имя пользователя. Я хочу подчеркнуть, что watch и computed — это не одно и то же, и этот пример просто используется для поддержки понимания функции.
Когда срабатывает watch
В предыдущем разделе мы узнали, что watchers
активно слушают определенные переменные и запускают свой метод, как только любая из этих переменных изменится.
В этом разделе мы собираемся проанализировать жизненный цикл Vue и понять, в каком состоянии эти функции действительно срабатывают. Незнание того, когда на самом деле срабатывает метод, обычно является результатом грязного кода и ненужных хаков.
Для облегчения понимания я собираюсь вставить часть диаграммы жизненного цикла из документации Vue:
https://vuejs.org/guide/essentials/lifecycle.html#lifecycle-diagramThe Причина, по которой я только что прошел среднюю часть жизненного цикла, заключается в том, что
watchers
срабатывают именно здесь одновременно с вызовом хуков жизненного цикла beforeUpdate
.
Для читателя, который только что впервые увидел эту диаграмму, жизненный цикл Mounted в середине изображения символизирует компонент, полностью загруженный и отображенный в DOM, а пунктирный круг вокруг него представляет цикл, который происходит при любом изменении реактивного свойства компонента (данные, свойство, вычисляемое).
Основная причина, по которой я хотел написать этот раздел, заключается в том, чтобы подчеркнуть два важных момента:
- Наблюдатели не вызываются при первом подключении компонента (для этого существует специальный флаг, который мы рассмотрим позже).
- Наблюдатели вызываются «до» того, как компонент будет перерендерирован. Поэтому в DOM все еще отображаются старые значения.
Давайте создадим простой хронологический список того, как все должно происходить:
- Экземпляр компонента называется
<myComponent firstName=.... />
. - Компонент монтируется и отображается в DOM — ПРИМЕЧАНИЕ: Часы НЕ вызываются!
- Свойство
firstName
изменяется родителем. - Жизненный цикл компонента начал цикл обновления
- Запускается метод Watch
- Компонент перерисовывается с новым значением
Как мы рассмотрим далее в статье, можно запустить эффект Watch после того, как DOM будет перерендерирован, и нет необходимости создавать какой-либо специальный хак. Я знаю, что уже говорил выше, но это действительно важно понять, потому что код, включенный в метод watch, никогда не должен полагаться на обновленный DOM (поэтому мы не должны проверять DOM или его состояние).
Примеры из реальной жизни
Давайте рассмотрим пару примеров и узнаем больше об этой функции Vue Js. Как уже упоминалось в начале статьи, мы будем рассматривать только примеры Option API и определять их с помощью однофайлового компонента (SFC):
<script setup>
import { ref, watch } from 'vue'
const selected = ref(0);
watch(selected, ( newValue, oldValue ) => {
triggerLog(newValue);
})
</script>
В приведенном выше примере мы запускаем вызов журнала, как только данные selected
изменяются. Чтобы иметь возможность использовать watch
с помощью API композиции и синтаксиса сценария, мы должны сначала импортировать его из vue:
import { ref, watch } from 'vue'
После того как watch
импортирован, мы можем вызывать его один или несколько раз. Первым аргументом, принимаемым этой функцией, является фактический геттер ref, computed или store, который мы хотим наблюдать, в нашем случае selected
.
Второй аргумент — это функция обратного вызова, которую мы хотим запускать каждый раз, когда наблюдаемая переменная изменяется. Этот обратный вызов принимает два аргумента, первый аргумент включает новое значение наблюдаемой переменной, а второй — старое значение.
Выше был приведен простой пример, но еще не время начинать и представлять различные опции и возможности этой функции, начиная с множественных часов и встроенных геттеров.
Наблюдение за несколькими переменными и встроенный геттер
Как я уже сказал в начале этой статьи, я решил разделить документацию между Composition API и Options API из-за некоторых различий, которые сделали бы статью сложной для понимания.
Возможность наблюдать за несколькими переменными одновременно или устанавливать встроенный геттер доступна только в Composition API, и для достижения того же самого в Options API необходимо реализовать обходной путь.
Я использую watchers уже довольно давно, и я был очень рад, когда эта функция появилась в Vue 3, поскольку она была источником многословного и нечистого кода.
Давайте сначала проанализируем необходимость следить за несколькими переменными. Это очень распространенный сценарий при заполнении формы, которая должна выдать побочный эффект. Давайте повторим приведенный выше пример с несколькими дополнительными входами:
<script setup>
import { ref, watch } from 'vue'
const name = ref(''),
surname = ref('');
watch([ name, surname ], ( newValue ) => {
triggerLog(newValue); //newvalue is an array including both values
})
</script>
В приведенном выше примере мы использовали массив в качестве первого параметра нашей функции watch и передали ей несколько ссылок [ имя, фамилия ]
. Вторая часть выглядит идентично нашему первому примеру, но имеет скрытое отличие, поскольку значение «newValue» (и «oldValue», если бы мы использовали его), это не просто значение, которое изменилось, а массив, включающий все значения, которые мы наблюдаем.
Я собираюсь привести хронологический пример, чтобы помочь понять эти значения.
<script setup>
import { ref, watch } from 'vue'
const name = ref(''),
surname = ref('');
watch([ name, surname ], ( newValue, oldValue ) => {
triggerLog(newValue); //newvalue is an array including both values
})
</script>
// Name changes to Simone
//OUTPUT of newValue: ['Simone', '']
//OUTPUT of oldValue: ['','']
// Surname changes to Cuomo
//OUTPUT of newValue: ['Simone', 'Cuomo']
//OUTPUT of oldValue: ['Simone','']
Как видно из примера выше, значения newValue
и oldValue
включают все значения, которые мы наблюдаем, а не только те, которые мы изменяем. Я бы предложил использовать реструктуризацию массива для улучшения читабельности:
watch([ name, surname ], ( [ newName, newSurname] ) => {
...
})
Теперь пришло время представить второе улучшение — возможность передавать встроенные геттеры или вычисляемые свойства как часть нашего наблюдаемого значения.
<script setup>
import { ref, watch } from 'vue'
const age = ref(0);
watch(
() => age.value > 50,
( newValue ) => {
triggerLog(newValue);
})
</script>
В приведенном выше примере мы собираемся вызвать журнал только в том случае, если значение age больше 50. Эта возможность была доступна в Option API, используя computed, но возможность объявлять эти геттеры непосредственно в функции Watch действительно улучшит опыт разработки.
Обратите внимание, что из-за того, что мы обращаемся к ссылке, мы должны использовать age.value
, как объясняется в документации Vue 3.
Очень важным замечанием при использовании встроенных геттеров является то, что наши часы будут срабатывать только при изменении возвращаемого значения наших геттеров. Это означает, что обратный вызов часов не будет изменен, если значение age меняется несколько раз, если значение не колеблется между значением 50. Например:
<script setup>
import { ref, watch } from 'vue'
const age = ref(0);
watch(
() => age.value > 50,
( newValue ) => {
triggerLog(newValue);
})
</script>
// Age change to 20;
// Watch NOT triggered
// Age change to 40;
// Watch NOT triggered
// Age change to 60;
// Watch triggered
Прежде чем мы перейдем к следующим функциям, я хотел бы рассказать, что watch может принимать смесь геттеров и рефлексов в качестве части своего массива переменных наблюдателя:
watch(
[ simpleRef, storeGetters, () => age.value > 50 ],
( newValue ) => {
triggerLog(newValue);
})
Реактивные объекты Watch — AKA DEEP
До сих пор мы всегда рассматривали refs и getters, но метод watch
также способен поддерживать сложные объекты, как те, что объявлены с помощью reactive
.
В отличие от Option API, метод watch
способен обрабатывать сложные объекты и автоматически применять «глубокую» опцию, если он обнаруживает объект в процессе наблюдения значений:
var form = reactive( { name: '', surname: '' } );
watch(
form,
(newForm) => {
}
)
Важно понимать, что наблюдение объектов требует обхода свойств объекта, а это может быть очень сложным для больших объектов и должно использоваться с осторожностью. Наблюдение за большим объектом может привести к медленному и ресурсоемкому выполнению кода.
Немедленный триггер — он же немедленный
Настало время рассмотреть еще один сценарий, с которым мы, скорее всего, столкнемся во время реальной разработки приложения Vue 3. В этом разделе мы рассмотрим необходимость немедленного вызова нашего Watcher при монтировании. Обычно это необходимо, когда функция обратного вызова нужна для установки определенного состояния приложения и должна выполняться на всех итерациях кода, даже на первой.
Этого можно добиться двумя различными методами. Первый предполагает использование API Composition напрямую без использования «watch», а второй — с помощью нового метода под названием «watchEffect».
Непосредственное наблюдение
Благодаря синтаксическому сахару API композиции, решение этой проблемы фактически не требует какой-либо специфической функции, поскольку мы можем «запустить» наш обратный вызов вручную:
// Watchers triggered ONLY if the "src" variable changes from its first value
<script setup>
import { watch } from 'vue'
const imgSrc = defineProps(['src'])
watch( imgSrc, preloadImage );
</script>
// Watchers triggered on load too
<script setup>
import { watch } from 'vue'
const imgSrc = defineProps(['src'])
preloadImage( imgSrc );
watch( imgSrc, preloadImage );
</script>
Использование метода «watch» из коробки не обеспечит возможность его немедленного запуска, но использование API композиции делает код для достижения этого чрезвычайно простым. Единственное различие между двумя примерами выше заключается в добавлении ручного вызова метода «preloadImage». Из-за природы API композиции этот метод будет вызываться очень рано в жизненном цикле Vue (даже до того, как компонент будет смонтирован). Если метод должен выполняться после того, как DOM будет полностью отрисован, нам нужно будет обернуть его в обратный вызов «onMounted»:
<script setup>
import { watch, onMounted } from 'vue'
const imgSrc = defineProps(['src'])
onMounted( () => {
preloadImage( imgSrc );
} );
watch( imgSrc, preloadImage );
</script>
WatchEffect
В этой статье я расскажу только о базовой части этой функции, так как лично я считаю ее довольно сложной и не хотел бы делать эту статью слишком сложной, так как она предназначена для новичков.
Эффект watchEffect
— это другая итерация watch
, которая запускается сразу при первом рендеринге компонента. Если вы когда-либо использовали API композиции, watchEffect
будет похож на использование опции immediate
.
Как уже упоминалось выше, я намеренно избегаю предоставления дополнительной информации и примера кода в этом посте.
Запуск после манипуляций с DOM — AKA Flush
Мы подошли к последней опции, доступной в рамках этой функции Vue Js. Как мы уже говорили, watch
запускается до того, как компонент будет полностью перерендерирован, но это можно изменить, используя конфигурацию «flush».
Использование «flush» гарантирует, что наш наблюдатель будет вызван после того, как компонент будет полностью перерендерирован, и его следует использовать для методов, которые требуют полного обновления DOM новыми значениями.
watch(
user,
( newValue ) => {
this.$refs.test.style = ....
},
{ flush: 'post'
}
)
Использование flush
очень важно, когда побочный эффект, включенный в обратный вызов watch, требует информации, включенной в DOM. Некоторые разработчики ошибочно используют «nextTick» внутри эффекта watch
для преодоления этой проблемы, но использование опции flush
на самом деле является предпочтительным вариантом.
Резюме
Я использую Vue JS уже много лет, но только недавно мне стало известно обо всех методах, доступных при использовании функции watchers
. Надеюсь, вышеприведенное сообщение поможет вам правильно использовать эту функцию и избежать халтурных решений проблем, которые можно легко устранить с помощью одной настройки.
Пришло время попрощаться, и как всегда, пожалуйста, оставьте мне комментарий или отзыв, чтобы улучшить этот пост для будущих читателей, а также подпишитесь на мою рассылку, чтобы получать уведомления о будущих постах.