Следим и вычисляем с Vue 3, или Как использовать watchEffect

от автора

Привет! Меня зовут Алексей, я frontend-специалист SimbirSoft. В этой статье разберем новый метод слежения за реактивными свойствами watchEffect.

С появлением Vue 3 c Composition API стало доступно два метода слежения — watch и watchEffect. Если «старый» метод watch всем хорошо знаком и не должен вызывать затруднений у Vue-разработчиков, то новый метод стоит изучить подробнее. Материал будет полезен разработчикам, переходящим с Vue 2 на Vue 3 и всем «вьюшникам», которые еще не разобрались с этим методом.

Composition API предоставляет нам два разных метода слежения за реактивными свойствами — watch и watchEffect. Они похожи, но все же каждый полезен в определенных случаях. Рассмотрим, какие сходства и различия существуют у этих методов:

Общее

Отличия

●следят за изменениями зависимостей
●выполняют побочные эффекты в отдельной функции коллбэка
●предоставляют способ остановки слежения

●watch (без immediate) может использоваться для ленивого запуска побочных эффектов (watchEffect всегда отрабатывает немедленно после монтажа компонента)
●watchEffect автоматически следит за изменениями любых состояний (может быть несколько переменных для отслеживания)
●watch обеспечивает доступ к текущим и предыдущим значениям

Это были основные отличия, о других мы расскажем ниже.

Слежение за объектами и массивами в 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().

Подписывайся на наши соцсети! Авторские материалы для frontend-разработчиков мы также публикуем в ВК и Telegram.


ссылка на оригинал статьи https://habr.com/ru/company/simbirsoft/blog/697910/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *