Реактивность под капотом и ловушка деструктуризации
Заглядываем под капот реактивности и разбираем ошибку №1 новичков — деструктуризацию reactive-объекта, после которой интерфейс перестаёт обновляться.
Реактивность во Vue 3 построена на
Proxy: Vue оборачивает объект и перехватывает чтение и запись свойств, чтобы знать, где значение используется, и что обновлять при изменении.
Как Vue «узнаёт» об изменениях
Когда вы создаёте reactive({ count: 0 }), Vue возвращает не сам объект, а Proxy вокруг него. Любое чтение свойства Vue запоминает («этот кусок шаблона зависит от count»), а любая запись — запускает обновление зависимых мест. Упрощённая модель такого перехвата:
function makeReactive(obj, onChange) {
return new Proxy(obj, {
set(target, key, value) {
target[key] = value;
onChange(key, value); // Vue здесь запускает перерисовку
return true;
},
});
}
const state = makeReactive({ count: 0 }, (k, v) =>
console.log(`Свойство ${k} стало ${v}`)
);
state.count = 1;
state.count = 2;
console.log("Итог:", state.count);
Вывод:
Свойство count стало 1 Свойство count стало 2 Итог: 2
Ключевая мысль: реактивность живёт в самом прокси-объекте. Перехват срабатывает, только когда вы обращаетесь к свойству через этот объект (state.count).
Ловушка: деструктуризация reactive
Теперь главная ошибка. Деструктуризация вынимает значение свойства в обычную переменную — и связь с прокси теряется. Обновление такой переменной Vue уже не видит:
function makeReactive(obj, onChange) {
return new Proxy(obj, {
set(t, k, v) { t[k] = v; onChange(k, v); return true; },
});
}
const state = makeReactive({ count: 0 }, () => console.log("перерисовка!"));
// Деструктуризация: count — теперь просто число 0, не связано с прокси
let { count } = state;
count = 5; // прокси не знает об этом — нет "перерисовка!"
console.log("Локальная count:", count);
console.log("В состоянии:", state.count); // там по-прежнему 0
Вывод:
Локальная count: 5 В состоянии: 0
Видите: мы поменяли локальную переменную, но прокси не сработал, и реальное состояние осталось прежним. В реальном Vue это выглядит так: «я меняю переменную, а на экране ничего не происходит». Это и есть классическая боль новичков с reactive.
Как делать правильно
| Подход | Реактивность |
Обращаться через объект: state.count | работает |
Деструктуризация: const { count } = state | теряется |
toRefs(state) перед деструктуризацией | сохраняется |
Использовать ref вместо reactive | проблемы нет |
Если нужно «разобрать» reactive-объект на переменные, оберните его в toRefs — он превратит каждое свойство в отдельный ref, сохранив связь:
<script setup>
import { reactive, toRefs } from 'vue'
const state = reactive({ count: 0, name: 'Аня' })
const { count, name } = toRefs(state) // count и name — реактивные ref
</script>
Почему ref проще
С ref этой ловушки нет: значение лежит в .value, и вы всегда работаете с обёрткой целиком. Это ещё один аргумент в пользу «ref по умолчанию» из прошлого урока.
Итог
- Реактивность Vue 3 построена на
Proxy: перехват чтения/записи свойств объекта. - Реактивность живёт в прокси-объекте; обращаться нужно через него (
state.count). - Деструктуризация
reactiveвынимает значение и рвёт связь — интерфейс перестаёт обновляться. - Решения:
toRefsперед деструктуризацией или использованиеref.