1. Основы Vue 3 и Composition API в контексте разработки на Nuxt
Основы Vue 3 и Composition API в контексте разработки на Nuxt
Когда разработчик впервые открывает файл компонента в современном проекте на Nuxt 3, он часто видит лаконичный блок <script setup>. В нем нет привычных по Vue 2 секций data, methods или computed. Вместо этого — россыпь переменных и функций, которые кажутся обычным JavaScript-кодом, но при этом магическим образом управляют интерфейсом. Почему индустрия отказалась от строгого структурирования Options API в пользу гибкости Composition API? Ответ кроется не в эстетике, а в масштабируемости и переиспользовании логики, что критично для сложных Nuxt-приложений.
От декларативности к функциональной гибкости
Долгое время Vue развивался в рамках Options API. Это была жесткая структура: состояние живет в data, логика трансформации — в computed, действия — в methods. Для небольших компонентов это работало идеально. Однако в крупных проектах возникала проблема «фрагментации логики». Если вам нужно реализовать поиск с фильтрацией, код этого функционала размазывался по всему компоненту: переменная запроса в одном месте, логика фильтрации в другом, слушатели событий в третьем.
Composition API, ставший фундаментом для Nuxt 3, меняет правила игры. Теперь мы группируем код не по типу (данные/методы), а по смыслу.
> «Composition API — это не просто новый синтаксис, это способ организации мышления. Мы перестаем описывать компонент как набор опций и начинаем описывать его как поток данных и зависимостей». > > Официальная документация Vue.js
В Nuxt 3 этот подход возведен в абсолют благодаря автоматическим импортам. Вам не нужно писать import { ref } from 'vue', Nuxt сделает это за вас. Это позволяет сфокусироваться на архитектуре, а не на обслуживании импортов.
Реактивность: ref против reactive
Сердце Composition API — система реактивности. Во Vue 3 она построена на JavaScript Proxy, что позволяет отслеживать изменения объектов и массивов гораздо эффективнее, чем в предыдущих версиях. Основными инструментами здесь выступают ref и reactive.
ref — это универсальный контейнер. Он принимает любое значение (примитив или объект) и возвращает реактивный объект с единственным свойством .value.
В шаблоне (template) Nuxt автоматически «распаковывает» ref, поэтому писать .value там не нужно. Это упрощает код, но иногда создает путаницу у новичков: почему в одном месте мы пишем имя переменной, а в другом добавляем свойство? Важно помнить: .value — это цена, которую мы платим за возможность передавать примитивы (числа, строки) по ссылке, сохраняя их реактивность.
reactive, напротив, работает только с объектами. Он делает сам объект реактивным «изнутри».
Казалось бы, reactive удобнее. Однако у него есть критический недостаток: при деструктуризации объекта реактивность теряется. Если вы напишете const { count } = state, переменная count станет обычным числом, которое не будет обновлять интерфейс. Именно поэтому в экосистеме Nuxt и Vue 3 общепринятым стандартом стало использование ref даже для объектов. Это обеспечивает единообразие и предсказуемость кода.
Вычисляемые свойства и побочные эффекты
Одной из самых мощных концепций остаются computed свойства. В Composition API это функция, которая возвращает реактивную ссылку, значение которой зависит от других реактивных данных.
Рассмотрим пример корзины товаров в интернет-магазине на Nuxt:
Здесь totalCost будет пересчитываться только тогда, когда изменится содержимое cart. Если мы обратимся к totalCost десять раз в шаблоне, функция выполнится лишь однажды, а результат будет взят из кэша. Это критически важно для производительности SSR (Server Side Rendering) в Nuxt, где лишние вычисления на сервере замедляют ответ пользователю.
Для ситуаций, когда нам нужно выполнить действие в ответ на изменение данных (например, сохранить что-то в localStorage или отправить запрос к API), используется watch.
В Nuxt 3 часто используется вариация watchEffect. Она автоматически отслеживает все реактивные зависимости, используемые внутри нее. Это удобно, но требует осторожности: легко создать бесконечный цикл, если внутри watchEffect неявно менять те же данные, от которых он зависит.
Магия <script setup> и жизненный цикл
В Nuxt 3 использование <script setup> является рекомендуемым стандартом. Это синтаксический сахар, который делает код чище: все объявленные переменные и функции автоматически доступны в шаблоне. Больше не нужно возвращать объект из функции setup().
Однако переход на Composition API изменил и восприятие жизненного цикла компонента. Больше нет beforeCreate и created — их код просто пишется непосредственно в блоке <script setup>.
Основные хуки теперь импортируются с префиксом on:
onMounted: вызывается, когда компонент появился в DOM. В Nuxt это происходит только на стороне клиента.onUnmounted: очистка ресурсов (таймеры, подписки).onUpdated: после обновления DOM.Важный нюанс Nuxt: так как приложение работает в режиме SSR, код внутри <script setup> выполняется дважды — на сервере и на клиенте. Если вы попытаетесь обратиться к window или document прямо в теле скрипта, серверная часть Nuxt выдаст ошибку ReferenceError: window is not defined. Именно поэтому работа с браузерными API всегда должна выноситься в onMounted.
Composables: архитектурный фундамент Nuxt
Самая сильная сторона Composition API — это возможность выносить логику в отдельные функции, называемые «композаблами» (composables). В Nuxt 3 для них выделена специальная папка composables/, содержимое которой импортируется автоматически.
Представьте, что вам нужно отслеживать координаты мыши или состояние авторизации в нескольких компонентах. Вместо копирования кода, вы создаете файл useAuth.ts:
Теперь в любом компоненте Nuxt вы просто пишете const { user, login } = useAuth(). Это и есть настоящая инкапсуляция. Мы не просто делим код на части, мы создаем независимые модули логики, которые легко тестировать и поддерживать.
Управление состоянием: useState и специфика SSR
В обычном Vue-приложении для глобального состояния часто используют ref вне компонентов. В Nuxt 3 это опасный путь. Из-за особенностей работы Node.js сервера, переменная, объявленная вне функции (в глобальной области видимости модуля), может стать общей для всех пользователей, заходящих на сайт. Это приведет к утечке данных: пользователь А увидит в профиле данные пользователя Б.
Для решения этой проблемы Nuxt предлагает функцию useState.
Где:
key: уникальная строка-идентификатор состояния.initFn: функция, возвращающая начальное значение.useState гарантирует, что состояние будет уникальным для каждого серверного запроса и правильно «гидрируется» (переносится с сервера на клиент) без расхождений в данных.
Сравнение подходов к организации кода
Для наглядности сравним, как одна и та же задача (счетчик с удвоением) выглядит в разных парадигмах.
| Характеристика | Options API | Composition API (<script setup>) | | :--- | :--- | :--- | | Организация | По типам (data, methods) | По логическим фичам | | Читаемость | Хорошая для простых задач | Отличная для сложных систем | | Переиспользование | Mixins (проблема конфликтов имен) | Composables (явный импорт и типизация) | | Типизация (TS) | Требует сложных оберток | Нативная и бесшовная | | Объем кода | Больше шаблонного кода (boilerplate) | Минималистичный |
Практический пример: сложная логика в Nuxt
Разберем сценарий реализации формы поиска с автодополнением и отменой предыдущего запроса. В Nuxt 3 это требует интеграции реактивности и механизмов очистки.
В этом примере мы видим, как ref управляет состоянием ввода, watch обрабатывает задержку запроса, а computed моментально реагирует на изменение массива результатов. Весь функционал сосредоточен в одном месте, его легко прочитать сверху вниз.
Нюансы типизации и Props/Emits
В профессиональной разработке использование TypeScript обязательно. В <script setup> работа с пропсами (входными данными) и эмитами (событиями) стала гораздо элегантнее благодаря макросам defineProps и defineEmits.
Эти макросы не нужно импортировать. Они обрабатываются компилятором Vue на этапе сборки. Важно понимать, что props в Composition API являются реактивным объектом. Если вы захотите деструктурировать их (const { title } = props), вы снова потеряете реактивность. Чтобы этого избежать, используйте toRefs(props).
Взаимодействие компонентов: Provide / Inject
Когда приложение на Nuxt разрастается, передача данных через пропсы на 5 уровней вниз (Prop Drilling) становится кошмаром. В Composition API для этого есть механизм provide и inject.
Родительский компонент (или даже плагин Nuxt) может «предоставить» данные:
Любой глубоко вложенный дочерний компонент может их «потребить»:
Это создает систему неявных зависимостей, поэтому использовать этот механизм стоит аккуратно, отдавая предпочтение Composables или Pinia для глобальных данных.
Оптимизация и производительность
Nuxt 3 активно использует статический анализ кода. Благодаря Composition API, сборщик Vite может эффективно проводить "tree-shaking" — удалять неиспользуемый код. Если вы не используете watch в своем компоненте, код, отвечающий за работу слушателей, не попадет в бандл этого компонента.
Также стоит упомянуть shallowRef. Если у вас есть огромный массив данных (например, тысячи строк из базы), обычный ref сделает каждое свойство каждого объекта реактивным, что создаст нагрузку на память. shallowRef сделает реактивной только саму ссылку на массив. Это один из тех профессиональных приемов, которые отличают опытного Nuxt-разработчика от новичка.
Резюмируя философию разработки
Переход на Nuxt 3 и Composition API — это не просто смена синтаксиса. Это переход к модульной архитектуре, где каждый кусок логики может быть изолирован и переиспользован. Мы уходим от "черных ящиков" компонентов к прозрачным потокам данных.
Понимание того, как работают ref, computed и watch, как useState предотвращает утечки данных в SSR и как правильно организовывать код в композаблах, закладывает фундамент для изучения более сложных тем: роутинга, серверных эндпоинтов и оптимизации рендеринга. В следующих главах мы увидим, как эти базовые кирпичики складываются в мощную структуру файловой системы Nuxt и обеспечивают бесшовную работу приложения.