Pull to refresh
67.82
SimbirSoft
Лидер в разработке современных ИТ-решений на заказ

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

Reading time6 min
Views21K

Привет! Меня зовут Алексей, я 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().

Спасибо за внимание! Другие наши материалы на Habr о Vue:

Хочу перемен: почему пора переходить на Vue 3

Настройка ESLint для чистого кода в проектах на Vue

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

Tags:
Hubs:
Total votes 4: ↑3 and ↓1+3
Comments5

Articles

Information

Website
www.simbirsoft.com
Registered
Founded
Employees
1,001–5,000 employees
Location
Россия