Skip to Content
VueРеактивность

Реактивность

Уровень: middle.

Вопрос

Как Vue 3 узнаёт, что шаблон надо обновить, и когда реактивность молча не срабатывает?

Короткий ответ

reactive оборачивает объект в Proxy. Чтение свойства внутри эффекта (рендер, computed, watch) подписывает эффект на это свойство. Запись уведомляет подписчиков.

ref кладёт значение в объект с полем .value и делает реактивным его. В шаблоне ref разворачивается, .value писать не нужно. В скрипте нужно.

Во Vue 2 геттеры и сеттеры вешались на поля, которые уже были в data. Добавление ключа и замена элемента массива по индексу требовали Vue.set. Во Vue 3 Proxy видит и новые ключи, и операции с массивом.

Не отслеживаются:

  • деструктуризация реактивного объекта без toRefs — в локальные переменные попадают сырые значения;
  • замена реактивного объекта обычным, если вы потеряли ссылку на прокси;
  • поля Map и Set, если пользоваться ими не через реактивную обёртку Vue, которая перехватывает их методы.

Разбор

const state = reactive({ count: 0 }) const { count } = state // count больше не реактивен const { count: countRef } = toRefs(state) // countRef.value связан со state

ref берут и для объектов, если нужна одна привычка: значение всегда за .value, ссылку можно заменить целиком. reactive удобен для группы полей формы. Его нельзя переприсвоить (state = other рвёт связь с прокси) и нельзя деструктурировать без toRefs.

toRefs(object) делает ref на каждое поле, деструктуризация связь не теряет. toRef(object, 'key') — ref на одно поле, даже если ключа ещё нет. toValue() (Vue 3.3+) достаёт сырое значение из примитива, ref или геттера. Это вход гибкого composable.

computed пересчитывается, когда читают его значение и грязные зависимости. Не читаемый computed работу не делает. watch по умолчанию ленивый и даёт новое и старое значение. watchEffect бежит сразу и собирает зависимости сам, старого значения у него нет. customRef нужен, когда момент track / trigger надо задать руками, например debounce поля ввода.

Глубокий watch(() => state, cb, { deep: true }) дорогой на больших деревьях. Лучше следить за конкретным полем или за геттером, который возвращает нужный срез.

Проп нельзя мутировать: изменение поля пропа во Vue 3 в режиме разработки предупреждает. Данные поднимают событием наверх или копируют в локальный ref, если это черновик формы.

На что смотрят на senior

Реактивное дерево, которое передают в большую таблицу целиком, будит лишние эффекты. Узкий computed для видимых строк и стабильные ключи списка важнее ручного «кеша». shallowRef уместен для неизменяемого снимка с сервера, который заменяют целиком: глубокая проксификация большого JSON ничего не даёт, если поля по отдельности не меняют.

markRaw помечает объект, который нельзя оборачивать: экземпляр карты, класс из сторонней библиотеки. Иначе Vue сделает из него прокси и сломает идентичность.

Типичные ошибки

  • Забыть .value у ref в скрипте и записать новое число мимо реактивности.
  • Деструктурировать props в setup и удивляться, что значения не обновляются.
  • Вешать глубокий watch на весь стор.
  • Ждать Vue.set и писать его в коде Vue 3.

Задача

Шаблон не обновляется. Где пропадает реактивность?

const state = reactive({ count: 0 }) const { count } = state function inc() { count += 1 }
Показать ответ

Деструктуризация снимает число с прокси. count — обычная переменная, count += 1 не пишет в state.count.

Нужно менять state.count или взять const { count } = toRefs(state) и писать count.value. Полная замена state = { count: 1 } тоже оторвёт шаблон: прокси останется старым.

Last updated on