Vue 3 Composition API: От Options к Composition на живых примерах

Vue 3 Composition API: От Options к Composition на живых примерах

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

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

Composition API — это набор функций, прежде всего ref(), reactive(), computed(), watch() и хуки жизненного цикла (onMounted, onUpdated и др.), которые вы импортируете и используете внутри функции setup(). Основная идея — группировать логику по функциональности, а не по типу (data, methods). Это особенно ценно в сложных компонентах, где связанный код раскидан по разным секциям Options API.

Ключевое отличие: Options API группирует код по типу опции (все data вместе, все methods вместе). Composition API позволяет группировать код по логической ответственности (вся логика пользователя вместе, вся логика загрузки данных вместе).

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

Вариант с Options API (Vue 2 стиль)

export default {
  data() {
    return {
      count: 0,
      message: 'Привет Vue!'
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  computed: {
    doubledCount() {
      return this.count * 2
    }
  },
  mounted() {
    console.log('Компонент создан')
  }
}

Тот же компонент на Composition API

import { ref, computed, onMounted } from 'vue'

export default {
  setup() {
    // Реактивные данные
    const count = ref(0)
    const message = ref('Привет Vue!')

    // Методы
    const increment = () => {
      count.value++ // Обратите внимание на .value
    }

    // Вычисляемое свойство
    const doubledCount = computed(() => count.value * 2)

    // Хук жизненного цикла
    onMounted(() => {
      console.log('Компонент создан')
    })

    // Всё, что возвращается из setup(), доступно в шаблоне
    return {
      count,
      message,
      increment,
      doubledCount
    }
  }
}

Запомните: ref() используется для примитивов (числа, строки) и объектов. Для доступа к значению внутри JS используйте .value, в шаблоне Vue делает это автоматически. Для сложных объектов можно использовать reactive(), но тогда нельзя деструктурировать без потери реактивности.

Продвинутые примеры: композируемые функции (Composables)

Настоящая сила Composition API раскрывается в возможности создавать переиспользуемые функции с собственной реактивной логикой — composables. Это похоже на миксины, но без их недостатков (конфликты имён, неявные зависимости).

Пример: композируемая функция useMouseTracker

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

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

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

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

  return { x, y }
}

// Компонент, использующий эту логику
import { useMouseTracker } from './composables/useMouseTracker'

export default {
  setup() {
    const { x, y } = useMouseTracker()

    return { x, y }
  }
}

Пример: композируемая функция для работы с API

// composables/useFetch.js
import { ref, onMounted } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const loading = ref(true)
  const error = ref(null)

  const fetchData = async () => {
    try {
      const response = await fetch(url)
      data.value = await response.json()
    } catch (err) {
      error.value = err
    } finally {
      loading.value = false
    }
  }

  onMounted(fetchData)

  return { data, loading, error, refetch: fetchData }
}

// Использование в компоненте
import { useFetch } from './composables/useFetch'

export default {
  setup() {
    const { data: posts, loading, error } = useFetch('https://api.example.com/posts')

    return { posts, loading, error }
  }
}

Работа с реактивными объектами: reactive()

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

import { reactive, toRefs } from 'vue'

export default {
  setup() {
    const state = reactive({
      user: {
        name: 'Анна',
        age: 28
      },
      preferences: {
        theme: 'dark',
        language: 'ru'
      }
    })

    // Важно! Если нужно вернуть отдельные свойства в template,
    // используйте toRefs() для сохранения реактивности
    return {
      ...toRefs(state)
      // Или можно вернуть весь объект state и обращаться в шаблоне как state.user.name
    }
  }
}

Используйте toRefs() при деструктуризации объекта, созданного через reactive(), чтобы сохранить реактивность каждого свойства. Без этого деструктурированные переменные станут обычными, нереактивными значениями.

Watch и WatchEffect: отслеживание изменений

Composition API предлагает две функции для наблюдения:

  • watch() — отслеживает конкретные источники данных и выполняет функцию при их изменении.
  • watchEffect() — автоматически отслеживает все реактивные зависимости внутри функции и выполняет её при изменении любой из них.
import { ref, watch, watchEffect } from 'vue'

export default {
  setup() {
    const searchQuery = ref('')
    const results = ref([])

    // watch — явное указание, что отслеживать
    watch(searchQuery, async (newQuery) => {
      if (newQuery.length > 2) {
        results.value = await fetchResults(newQuery)
      }
    }, { immediate: true }) // Опция immediate запускает watcher сразу

    // watchEffect — автоматическое отслеживание зависимостей
    const count = ref(0)
    watchEffect(() => {
      console.log(`count изменился на: ${count.value}`)
      // Автоматически отслеживает count.value
    })

    return { searchQuery, results, count }
  }
}

Практические советы по переходу на Composition API

  1. Начинайте с новых компонентов — не переписывайте старые рабочие компоненты без необходимости.
  2. Используйте <script setup> синтаксис — это ещё более лаконичный способ работы с Composition API (доступен в Vue 3.2+).
  3. Создавайте composables для общей логики — это улучшит переиспользование кода и его тестируемость.
  4. Комбинируйте с Options API — Composition API можно использовать внутри setup() в компонентах, которые также используют Options API.

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

Composition API заменяет Options API?

Нет, Composition API является дополнением, а не заменой. Options API остаётся полностью поддерживаемым и отлично подходит для простых компонентов. Composition API решает проблемы масштабируемости сложных компонентов.

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

Абсолютно нет. Vue 3 полностью поддерживает Options API. Переписывайте только те компоненты, которые стали слишком сложными или где вы хотите вынести логику в переиспользуемые composables.

Что лучше: ref() или reactive()?

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

Можно ли использовать Composition API с Vuex/Pinia?

Да, абсолютно. Composition API прекрасно работает с менеджерами состояния. Более того, Pinia, рекомендуемый store для Vue 3, активно использует Composition API в своём дизайне.

Сложнее ли учить Composition API новичкам?

Для полных новичков Options API может быть проще для понимания из-за чёткой структуры. Однако Composition API предлагает более фундаментальное понимание реактивности и в долгосрочной перспективе делает код более предсказуемым и поддерживаемым.