Vue 3 Composition API: Полный разбор с живыми примерами для перехода от Options API

Vue 3 Composition API: Полный разбор с живыми примерами для перехода от Options API

Если вы работаете с Vue.js, то Composition API — это не просто новая синтаксическая возможность, а фундаментальный сдвиг в мышлении. Он предлагает более гибкий и мощный способ организации логики компонентов, особенно когда они становятся сложными. Давайте разберемся, как он работает, и рассмотрим практические примеры, которые помогут вам освоить этот инструмент.

Что такое Composition API и зачем он нужен?

Composition API был представлен в Vue 3 как альтернатива классическому Options API (data, methods, computed). Его основная цель — решить проблему "распыления логики". В Options API код, относящийся к одной функциональности (например, работа с пользователем), мог быть разбросан по разным опциям: данные в data, методы в methods, а вычисляемые свойства в computed. В больших компонентах это усложняло понимание и поддержку.

Composition API построен вокруг реактивных ссылок (ref) и реактивных объектов (reactive), а также функций-композиций (composables), которые позволяют инкапсулировать и повторно использовать логику.

Базовый пример: от Options к Composition

Рассмотрим простой счетчик. Вот как он выглядел бы в Options API:

// Options API
<template>
  <button @click=\"increment\">{{ count }}</button>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

А теперь тот же компонент с использованием Composition API и <script setup> (упрощенный синтаксис):

// Composition API с <script setup>
<template>
  <button @click=\"increment\">{{ count }}</button>
</template>

<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++ // Обратите внимание на .value
}
</script>

Ключевые моменты:

  • ref() создает реактивную ссылку. Для примитивов (числа, строки) используется ref.
  • Чтобы получить или изменить значение, нужно обращаться к свойству .value внутри логики. В шаблоне Vue делает это автоматически.
  • <script setup> автоматически экспортирует все объявления верхнего уровня в шаблон.

Работа с реактивными объектами и вычисляемыми свойствами

Для объектов и массивов часто удобнее использовать reactive(). Рассмотрим пример формы пользователя с вычисляемым полным именем.

<script setup>
import { reactive, computed } from 'vue'

const user = reactive({
  firstName: 'Иван',
  lastName: 'Петров'
})

const fullName = computed(() => {
  return `${user.firstName} ${user.lastName}`
})

function updateName(newFirstName) {
  user.firstName = newFirstName
}
</script>

Функция computed создает реактивное вычисляемое свойство. Его значение кэшируется и пересчитывается только при изменении зависимых реактивных источников.

Мощь композаблов (Composables)

Истинная сила Composition API раскрывается в создании переиспользуемых функций логики — композаблов. Давайте создадим композабл для работы с состоянием мыши.

// useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  return { x, y }
}

Теперь используем его в компоненте:

<script setup>
import { useMouse } from './useMouse.js'

const { x, y } = useMouse()
</script>

<template>
  <p>Координаты мыши: {{ x }}, {{ y }}</p>
</template>

Логика инкапсулирована и может быть использована в любом компоненте! Таким же образом можно создавать композаблы для работы с API, локальным хранилищем, таймерами и т.д.

Жизненный цикл и watch

Хуки жизненного цикла импортируются как функции (onMounted, onUpdated, onUnmounted). Для отслеживания изменений используется watch и watchEffect.

<script setup>
import { ref, watch, onMounted } from 'vue'

const searchQuery = ref('')
const searchResults = ref([])

// watch следит за конкретным источником
watch(searchQuery, async (newQuery) => {
  if (!newQuery) {
    searchResults.value = []
    return
  }
  // Имитация запроса к API
  searchResults.value = await fetchResults(newQuery)
})

// watchEffect запускается сразу и отслеживает все используемые внутри реактивные зависимости
watchEffect(() => {
  console.log(`Запрос изменился на: ${searchQuery.value}`)
})

onMounted(() => {
  console.log('Компонент смонтирован')
})
</script>

Практический пример: Компонент поиска с debounce

Соберем все вместе в полезном примере — поле поиска с задержкой (debounce), чтобы не делать запрос на каждый ввод символа.

<template>
  <input v-model=\"inputValue\" placeholder=\"Поиск...\" />
  <ul>
    <li v-for=\"item in results\" :key=\"item.id\">{{ item.name }}</li>
  </ul>
</template>

<script setup>
import { ref, watch } from 'vue'

const inputValue = ref('')
const results = ref([])
let timeoutId = null

watch(inputValue, (newVal) => {
  // Очищаем предыдущий таймер
  clearTimeout(timeoutId)
  // Устанавливаем новый таймер на 500 мс
  timeoutId = setTimeout(async () => {
    if (newVal.trim()) {
      results.value = await performSearch(newVal)
    } else {
      results.value = []
    }
  }, 500)
})

async function performSearch(query) {
  // Заглушка для реального API-запроса
  const response = await fetch(`/api/search?q=${query}`)
  return response.json()
}
</script>

FAQ: Часто задаваемые вопросы о Composition API

Нужно ли полностью переписывать старые проекты на Composition API?

Нет. Composition API является дополнительной, а не обязательной системой. Vue 3 полностью поддерживает Options API. Переход можно делать постепенно, например, в новых компонентах или при рефакторинге сложной логики.

Что лучше использовать: ref или reactive?

ref универсальна и подходит для любых типов данных, но требует обращения через .value. reactive работает только с объектами, но позволяет обращаться к свойствам напрямую. Часто выбор — вопрос стиля. Многие разработчики предпочитают использовать ref для consistency, а для сложных структур состояния — reactive.

Composition API сложнее для новичков?

Options API изначально может быть более интуитивным благодаря четкой структуре. Composition API требует большего понимания реактивности и JavaScript. Однако для средних и крупных проектов он быстро окупается за счет лучшей организации кода и переиспользуемости логики.

Можно ли миксовать Composition и Options API в одном компоненте?

Да, но только если вы не используете <script setup>. В обычном компоненте с setup() хуком можно использовать Composition API, а остальные опции (например, components или inheritAttrs) оставить в Options API. Однако в <script setup> миксование невозможно.