# Вычисляемые свойства и методы-наблюдатели
Этот раздел использует синтаксис однофайловых компонентов для примеров кода
# Вычисляемые значения
Иногда требуется состояние, зависящее от другого состояния — во Vue это реализуется с помощью свойства computed компонента. Для создания вычисляемого значения напрямую можно использовать метод computed
: он получает функцию геттер и возвращает реактивный иммутабельный ref объект для возвращаемого значения из геттера.
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // ошибка
2
3
4
5
6
Кроме того, можно передать объект с функциями get
и set
для создания изменяемого ref объекта.
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
2
3
4
5
6
7
8
9
10
# watchEffect
Для применения и автоматического повторного применения побочного эффекта, который основан на реактивном состоянии, можно использовать метод watchEffect
. Он запускает функцию немедленно при отслеживании своих зависимостей и повторно запустит её при изменении одной из зависимостей.
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> выведет в консоль 0
setTimeout(() => {
count.value++
// -> выведет в консоль 1
}, 100)
2
3
4
5
6
7
8
9
# Остановка отслеживания
Когда watchEffect
вызывается во время работы функции setup() компонента или хуков жизненного цикла, то он привязывается к жизненному циклу компонента и автоматически останавливается при размонтировании компонента.
Для остальных случаев, он возвращает метод, который может быть вызван для явной остановки отслеживания:
const stop = watchEffect(() => {
/* ... */
})
// позднее
stop()
2
3
4
5
6
# Аннулирование побочных эффектов
Иногда функция наблюдателя может выполнять асинхронные побочные эффекты, которые требует дополнительных действий, при их аннулировании (т.е. в случаях, когда состояние изменилось до того, как эффекты завершились). Функция эффекта принимает функцию onInvalidate
, которая будет использована для аннулирования действий и вызывается:
- когда эффект будет вскоре запущен повторно
- когда наблюдатель остановлен (т.е. когда компонент размонтирован, если
watchEffect
используется внутриsetup()
или хука жизненного цикла)
watchEffect(onInvalidate => {
const token = performAsyncOperation(id.value)
onInvalidate(() => {
// id был изменён или наблюдатель остановлен.
// аннулирование выполняемой асинхронной операции
token.cancel()
})
})
2
3
4
5
6
7
8
9
Коллбэк для аннулирования регистрируется передачей функции внутрь, а не возвращением её из коллбэка, потому что возвращаемое значение важно для обработки асинхронных ошибок. Очень часто функция эффекта будет асинхронной при операциях загрузки данных:
const data = ref(null)
watchEffect(async (onInvalidate) => {
onInvalidate(() => { /* ... */ }) // регистрируем функцию перед разрешением Promise
data.value = await fetchData(props.id)
})
2
3
4
5
6
Асинхронная функция неявно возвращает Promise, но функцию для очистки необходимо зарегистрировать перед тем, как разрешится Promise. Кроме того, Vue полагается на возвращаемый Promise для автоматической обработки потенциальных ошибок в цепочке Promise.
# Синхронизация времени очистки эффектов
Система реактивности Vue буферизирует аннулированные эффекты и выполняет их очистку асинхронно. Это сделано для избежания дублирующих вызовов, когда в одном «тике» происходит много изменений состояния. Внутренняя функция update
компонента также является эффектом. Когда пользовательский эффект добавляется в очередь, то по умолчанию он будет вызываться перед всеми эффектами update
компонента:
<template>
<div>{{ count }}</div>
</template>
<script>
export default {
setup() {
const count = ref(0)
watchEffect(() => {
console.log(count.value)
})
return {
count
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
В этом примере:
- Счётчик будет выведен в консоль синхронно при первом запуске.
- При изменениях
count
, коллбэк будет вызываться перед обновлением компонента.
В случаях, когда эффект наблюдателя требуется повторно запустить после обновления компонента (например, при работе со ссылками на элемента шаблона), можно передать дополнительный объект настроек с опцией flush
(значение по умолчанию — 'pre'
):
// Вызовется после обновления компонента,
// поэтому можно получить доступ к обновлённому DOM
// Примечание: это также отложит первоначальный запуск эффекта
// до тех пор, пока первая отрисовка компонента не будет завершена.
watchEffect(
() => {
/* ... */
},
{
flush: 'post'
}
)
2
3
4
5
6
7
8
9
10
11
12
Опция flush
может также принимать значение 'sync'
, которое принудительно заставит эффект всегда срабатывать синхронно. Однако это неэффективно и должно использоваться крайне редко.
# Отладка наблюдателей
Опции onTrack
и onTrigger
можно использовать для отладки поведения наблюдателя.
onTrack
вызывается, когда реактивное свойство или ссылка начинает отслеживаться как зависимость.onTrigger
вызывается, когда коллбэк наблюдателя вызван изменением зависимости.
Оба коллбэка получают событие отладчика с информацией о зависимости, о которой идёт речь. Рекомендуем указывать debugger
в этих коллбэках для удобного инспектирования зависимости:
watchEffect(
() => {
/* побочный эффект */
},
{
onTrigger(e) {
debugger
}
}
)
2
3
4
5
6
7
8
9
10
Опции onTrack
и onTrigger
работают только в режиме разработки.
# watch
API watch
является точным эквивалентом свойства watch компонента. watch
требует наблюдения за конкретным источником данных и применяет побочные эффекты в отдельной функции коллбэка. Он также ленив по умолчанию — т.е. коллбэк вызывается только тогда, когда наблюдаемый источник изменился.
По сравнению с watchEffect,
watch
позволяет:- Лениво выполнять побочные эффекты;
- Конкретнее определять какое состояние должно вызывать перезапуск;
- Получать доступ к предыдущему и текущему значению наблюдаемого состояния.
# Отслеживание единственного источника данных
Источником данных для наблюдателя может быть функция геттер, возвращающая значение, или непосредственно реактивная ссылка ref
:
// наблюдение за геттер-функцией
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
// наблюдение за ref-ссылкой
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
2
3
4
5
6
7
8
9
10
11
12
13
14
# Отслеживание нескольких источников данных
Наблюдатель также может отслеживать несколько источников одновременно, используя запись с массивом:
const firstName = ref('');
const lastName = ref('');
watch([firstName, lastName], (newValues, prevValues) => {
console.log(newValues, prevValues);
})
firstName.value = "John"; // выведет в консоль: ["John",""] ["", ""]
lastName.value = "Smith"; // выведет в консоль: ["John", "Smith"] ["John", ""]
2
3
4
5
6
7
8
9
# Отслеживание реактивных объектов
Использование наблюдателя для сравнения значений массива или объекта, которые являются реактивными, требуют, чтобы у него была копия, состоящая только из значений.
const numbers = reactive([1, 2, 3, 4])
watch(
() => [...numbers],
(numbers, prevNumbers) => {
console.log(numbers, prevNumbers);
})
numbers.push(5) // Выведет в консоль: [1,2,3,4,5] [1,2,3,4]
2
3
4
5
6
7
8
9
Чтобы отслеживать изменения свойств в глубоко вложенном объекте или массиве нужно установить опцию deep
в значение true
:
const state = reactive({
id: 1,
attributes: {
name: "",
},
});
watch(
() => state,
(state, prevState) => {
console.log(
"без опции deep ",
state.attributes.name,
prevState.attributes.name
);
}
);
watch(
() => state,
(state, prevState) => {
console.log(
"с опцией deep ",
state.attributes.name,
prevState.attributes.name
);
},
{ deep: true }
);
state.attributes.name = "Alex"; // Logs: "deep " "Alex" "Alex"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Однако, при отслеживании реактивного объекта или массива будет всегда возвращаться одна ссылка на текущее значение этого объекта как для текущего, так и для предыдущего состояния. Для полноценного отслеживания глубоко вложенных объектов или массивов, может потребоваться создание глубокой копии значений. Это может сделать с помощью утилиты, такой как lodash.cloneDeep (opens new window)
import _ from 'lodash';
const state = reactive({
id: 1,
attributes: {
name: "",
},
});
watch(
() => _.cloneDeep(state),
(state, prevState) => {
console.log(
state.attributes.name,
prevState.attributes.name
);
}
);
state.attributes.name = "Alex"; // Выведет в консоль: "Alex" ""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Общее поведение с watchEffect
Общее поведение watch
и watchEffect
будет в возможностях остановки отслеживания, аннулировании побочных эффектов (с передачей коллбэка onInvalidate
третьим аргументом), синхронизации времени очистки эффектов и инструментов отладки.