Привет! Меня зовут Алексей, я frontend-специалист SimbirSoft. В этой статье разберем новый метод слежения за реактивными свойствами watchEffect.
С появлением Vue 3 c Composition API стало доступно два метода слежения — watch и watchEffect. Если «старый» метод watch всем хорошо знаком и не должен вызывать затруднений у Vue-разработчиков, то новый метод стоит изучить подробнее. Материал будет полезен разработчикам, переходящим с Vue 2 на Vue 3 и всем «вьюшникам», которые еще не разобрались с этим методом.
Composition API предоставляет нам два разных метода слежения за реактивными свойствами — watch и watchEffect. Они похожи, но все же каждый полезен в определенных случаях. Рассмотрим, какие сходства и различия существуют у этих методов:
Общее | Отличия |
● следят за изменениями зависимостей | ● watch (без immediate) может использоваться для ленивого запуска побочных эффектов (watchEffect всегда отрабатывает немедленно после монтажа компонента) |
Это были основные отличия, о других мы расскажем ниже.
Слежение за объектами и массивами в watchEffect
WatchEffect может отслеживать только адрес памяти реактивного объекта. Изменение элементов массива или свойств объекта не изменит адрес памяти и, следовательно, не вызовет срабатывания метода watchEffect:
<template>
<div class="home">
<button @click="addTarget">add</button>
<div>{{ target }}</div>
</div>
</template>
<script setup>
import { reactive, watchEffect } from "vue";
const target = reactive([1, 2, 3, 4]);
watchEffect(() => {
console.log(target);
});
const addTarget = () => {
target.push(5);
};
</script>
При нажатии на кнопку add видим, что данные в массив добавляются, но watchEffect отрабатывает только один раз. WatchEffect всегда срабатывает только один раз после монтажа компонента, в watch нам приходилось указывать для этого immediate — true:
Нам нужно преобразовать реактивный объект обратно в массив с помощью spread-оператора:
<template>
<div class="home">
<button @click="addTarget">add</button>
{{ target }}
</div>
</template>
<script setup>
import { reactive, watchEffect } from "vue";
const target = reactive([1, 2, 3, 4]);
watchEffect(() => {
const arr = [...target];
console.log(arr);
});
const addTarget = () => {
target.push(5);};
</script>
Теперь наш watchEffect успешно отрабатывает:
Для слежения за объектами будем использовать toRefs():
<template>
<div class="home">
<button @click="setTitle">changeTitle</button>
{{ data }}
</div>
</template>
<script setup>
import { reactive, watchEffect, toRefs } from "vue";
const data = reactive({
title: "Some title",
desc: "Some description",
});
watchEffect(() => {
const parse = toRefs(data);
console.log(parse);
});
const setTitle = () => {
data.title = "New title";
};
</script>
Вызов функции stop() остановит действие watchEffect:
<template>
<div class="home">
<button @click="setTitle">changeTitle</button>
<button @click="stop">stop</button>
<div>
{{ data }}
</div>
</div>
</template>
<script setup>
import { reactive, watchEffect, toRefs } from "vue";
const data = reactive({
title: "Some title",
desc: "Some description",
});
const stopWatchEffect = watchEffect(() => {
const parse = toRefs(data);
console.log(parse);
});
const setTitle = () => {
data.title = "New title";
};
const stop = () => {
stopWatchEffect();
};
</script>
Параметры watchEffect
Метод watchEffect принимает два аргумента. Первый — это коллбэк-функция. Второй — объект конфигурации:
watchEffect(
() => {},
{
flush: 'post',
onTrack(e) {
debugger
},
onTrigger(e) {
debugger
}
})
Свойство flush определяет, запускается ли метод watchEffect до, после или во время повторного рендеринга страницы:
flush: 'pre' | 'post' | 'sync'
По умолчанию созданные пользователем коллбек-функции наблюдателя вызываются до обновления компонентов Vue. Это означает, что если вы попытаетесь получить доступ к DOM внутри коллбек-функции наблюдателя, DOM будет в состоянии до того, как Vue применит какие-либо обновления.
Дополнительный объект настроек с опцией flush (значение по умолчанию — 'pre'):
let stop = watchEffect(callback, {
flush: 'pre'
})
Опция flush также может принимать значение 'sync', которое принудительно заставит эффект всегда срабатывать синхронно. Однако такое поведение неэффективно и должно использоваться только в крайних случаях:
watchEffect(callback, {
flush: 'sync'
})
Если вы хотите получить доступ к DOM в коллбек-функции наблюдателя после того, как Vue его обновил его, вам нужно указать flush: 'post':
watchEffect(callback, {
flush: 'post'
})
Отладка watchEffect
Можно использовать опции onTrack и onTrigger для отладки поведения наблюдателя:
onTrack вызывается, когда реактивное свойство или ссылка начинает отслеживаться как зависимость;
onTrigger вызывается, когда коллбэк наблюдателя вызван изменением зависимости.
Оба коллбэка получают событие отладчика с информацией о зависимости, о которой идет речь. Обратите внимание, опции onTrack и onTrigger работают только в режиме разработки.
Аннулирование побочных эффектов
Иногда в функции наблюдателя могут быть асинхронные побочные эффекты, которые требуют дополнительных действий при их аннулировании (то есть в случаях, когда состояние изменилось до того, как эффекты завершились). Для таких случаев функция эффекта принимает функцию onInvalidate. Она будет использоваться для аннулирования выполненного и вызываться в следующих случаях:
когда эффект будет вскоре запущен повторно;
когда наблюдатель остановлен (то есть когда компонент размонтирован, если watchEffect используется внутри setup() или хука жизненного цикла).
<template>
<div class="home">
<button @click="setTitle">changeTitle</button>
<button @click="stop">stop</button>
{{ data }}
</div>
</template>
<script setup>
import { reactive, watchEffect, toRefs } from "vue";
const data = reactive({
title: "Some title",
desc: "Some description",
});
const stopWatchEffect = watchEffect((onInvalidate) => {
const parse = toRefs(data);
console.log(parse);
console.log("basic function");
onInvalidate(() => {
console.log("onInvalidate function");
});
});
const setTitle = () => {
data.title = "New title";
};
const stop = () => {
stopWatchEffect();
};
</script>
Снова запустим наше приложение:
Видим, что сначала отработала основная коллбек-функция, и в консоли вывелось basic function.
Если мы нажмем на кнопку stop, то сработает функция onInvalidate:
Если нажать на кнопку change title, то вначале выполнится функция onInvalidate, а затем основная функция. При изменении свойств onInvalidate будет всегда срабатывать первой:
Практическое применение watchEffect
Мы будем отменять запросы axios, если входные данные изменились, и старые данные больше не актуальны:
<template>
<div class="home">
<div>
<button @click="setType('albums')">load albums</button>
<button @click="setType('posts')">load post</button>
<button @click="stop">stop</button>
</div>
<div>
{{ information }}
</div>
</div>
</template>
<script setup>
import { ref, watchEffect } from "vue";
import axios from "axios";
const type = ref("albums");
const information = ref(null);
const source = ref(null);
const setType = (value) => {
type.value = value;
};
const init = async function () {
source.value = axios.CancelToken.source();
try {
const data = await axios.get(
`https://jsonplaceholder.typicode.com/${type.value}`,
{
cancelToken: source.value.token,
}
);
return data;
} catch (error) {
console.log(error);
}
};
const stop = watchEffect((onInvalidate) => {
onInvalidate(() => {
source.value.cancel("Отмена запроса");
});
init(type.value).then((res) => {
information.value = res;
});
});
</script>
Функция setType изменит значение переменной type в зависимости от параметра, который был принят. WatchEffect следит за изменением переменной type, и при ее изменении делает запрос на получение постов или альбомов. Заметьте, мы нигде изначально не инициализируем запрос, поскольку watchEffect срабатывает один раз сразу при загрузке страницы:
Установим скорость сети в Slow 3G. И будем быстро переключаться по кнопкам load post и load alboms. Мы увидим, что запросы, которые больше не актуальны, отменяются.
Резюме
Мы разобрались с методом наблюдения за реактивными сущностями с помощью watchEffect.
Что следует запомнить о методе watchEffect:
он всегда срабатывает один раз после монтажа компонента;
он может отслеживать несколько переменных;
если вы хотите отслеживать изменения после обновления компонента, используйте опцию flush: 'post';
функция onInvalidate всегда срабатывает перед основной функцией;
для слежения за массивами используйте спред-оператор, а для слежения за объектами — функцию toRefs().
Спасибо за внимание! Другие наши материалы на Habr о Vue:
Хочу перемен: почему пора переходить на Vue 3
Настройка ESLint для чистого кода в проектах на Vue
Подписывайся на наши соцсети! Авторские материалы для frontend-разработчиков мы также публикуем во ВКонтакте и Telegram.