Содержание
Коротко
Vue 3 с Proxy, Composition API и встроенной поддержкой TypeScript выглядит проще Vue 2, но на практике «уметь пользоваться» и «использовать правильно» — разные уровни. На Dev.to собрали типичные ловушки реактивности, выбора между ref и reactive, наблюдателей и новых компонентов вроде Teleport и Suspense.
Что произошло
Реактивность переписали с Object.defineProperty на Proxy: исчезли классические боли Vue 2 — новые поля объекта и индексы массива без реакции. Зато появился новый ментальный налог: где хранится связь с обновлением DOM, если вы деструктурировали объект или подменили ссылку целиком.
Простое правило из статьи: ref — для примитивов и объектов, которые могут полностью замениться; reactive — для вложенных структур вроде форм и конфигов. Ловушка в том, что reactive возвращает прокси исходного объекта: при let { count } = state вы получаете обычное число, и инкремент не триггерит перерисовку. Выход — toRefs, чтобы каждое поле осталось ref-обёрткой.
Отдельная головная боль — автораспаковка ref. В шаблоне .value не нужен, но ref внутри обычного массива не распаковывается автоматически, а внутри reactive-массива — распаковывается. Ошибка «заменил весь объект reactive-переменной» лечится либо переходом на ref с присвоением data.value = …, либо Object.assign, сохраняющим ту же прокси-ссылку.
В Composition API автор сравнивает watch и watchEffect: второй сразу выполняется и сам собирает зависимости — удобно для логов, но опасно, если внутри менять те же ref (риск бесконечного цикла). Для контролируемых реакций на конкретное поле с «старым/новым» значением — watch.
Почему это важно
Vue 3 часто выбирают как «легче React», но половина багов в проде — не в API, а в разрыве реактивной цепочки: composable вернул .value вместо ref, lifecycle-хук зарегистрировали в setTimeout, scoped-стили не доходят до Teleport. Такие ошибки не ловятся линтером и проявляются только под нагрузкой или в SSR.
Для команд это означает: нужен явный контракт composable — «наружу только ref/reactive, не голые значения» — и ревью кода на деструктуризацию state. TypeScript в Vue 3 помогает, но только если props объявлять через defineProps<{…}>() и withDefaults, а не смешивать объявления на этапе выполнения и по типам без дисциплины.
На практике
- Деструктурируете
reactive— оборачивайте вtoRefs, иначе теряете реактивность. - Данные могут полностью замениться — берите
ref, неreactive. - В
watchEffectне мутируйте зависимости без явного условия выхода; для сравнения прежнего и нового значения —watch. - Composable
useXxxвозвращает{ x, y }как ref, не{ x: x.value }. - Хуки
onMountedи др. вызывайте синхронно в setup, не из async-колбэка. - Teleport переносит DOM, но контекст (props, inject) остаётся у родителя — для стилей нужен
:deep()или глобальный CSS. - Suspense и async-компоненты — осторожно в SSR и критичных маршрутах; пока экспериментальная зона.
Итог
Vue 3 не сложнее по синтаксису — сложнее по модели реактивности. Статья на Dev.to — хороший чеклист перед рефакторингом legacy на Composition API: ref/reactive, watch, composable-контракты и краевые случаи Teleport. Если ловили похожие баги — сверьтесь с оригиналом и примерами кода.