логотип PurpleSchool
логотип PurpleSchool

Понимание и применение Composition API в Vue 3

Автор

Олег Марков

Введение

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

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

Для чего нужен Composition API

Кратко о мотивации появления

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

  • Трудно организовать и переиспользовать логику. Функции, связанные по смыслу, могут быть разбросаны между разными разделами компонента.
  • Переиспользование логики — только миксины. А миксины имеют массу недостатков: коллизии имён, запутанность зависимостей, неявные связи.

Composition API предлагает решение этих проблем.

Ключевые преимущества

  • Гибкость: вы сами решаете, как структурировать код.
  • Явные зависимости: видно, какие данные и функции используются и возвращаются из компонента.
  • Удобное переиспользование логики: с помощью composables — простых функций, которые вы можете вызывать повторно.

Давайте теперь перейдём к практике.

Основные концепции Composition API

setup функция — точка входа

Всё начинается с функции setup. Она вызывается сразу после создания экземпляра компонента, но до вычисления шаблона.

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

// Создаем реактивное состояние
const count = ref(0)

// Функция для увеличения значения
function increment() {
  count.value++
}
</script>

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

// Здесь создается переменная count как реактивное состояние, функция increment изменяет значение. Всё, что объявлено в setup, будет доступно внутри шаблона (в <script setup> это происходит автоматически).

Когда используется обычная форма setup

Если не использовать <script setup>, функция выглядит так:

export default {
  setup() {
    const count = ref(0)
    function increment() {
      count.value++
    }
    return { count, increment }
  }
}

// Объекты, возвращённые из setup, становятся доступны в шаблоне.

Работа с реактивностью: ref и reactive

ref — для простых типов

ref создаёт реактивную ссылку на примитив или объект. Чтобы получить или изменить значение, используйте свойство .value.

import { ref } from 'vue'
const message = ref('Привет, Vue!')
message.value = 'Новый текст'

// Вы в любой момент можете присваивать новое значение через .value.

reactive — для объектов и массивов

Если работать хочется с объектами или массивами, удобней использовать reactive — он превращает весь объект целиком в реактивный:

import { reactive } from 'vue'
const state = reactive({
  counter: 0,
  user: {
    name: 'Аня'
  }
})

state.counter++           // Автоматически обновит шаблон
state.user.name = 'Иван'

// Здесь весь state становится реактивным, все его свойства отслеживаются Vue.

computed — вычисляемые значения

В Composition API вычисляемые значения создаются функцией computed. Это удобно для зависимых данных.

import { ref, computed } from 'vue'

const count = ref(2)
const double = computed(() => count.value * 2)

// double будет обновляться автоматически, если поменяется count.value.

watch и watchEffect — отслеживание изменений

Иногда нужно следить за изменениями переменных и запускать побочный эффект.

watch

watch позволяет следить за одной или несколькими реактивными переменными.

import { ref, watch } from 'vue'

const name = ref('Аня')
watch(name, (newValue, oldValue) => {
  // Будет вызван при каждом изменении name.value
  console.log(`Имя изменилось: ${oldValue} → ${newValue}`)
})

// Можно следить и за computed, и за ref, и за массивами.

watchEffect

watchEffect отслеживает все реактивные переменные, которые используются внутри его функции.

import { ref, watchEffect } from 'vue'

const price = ref(100)
watchEffect(() => {
  console.log(`Актуальная цена: ${price.value}`)
})

// Эффект будет запускаться каждый раз, когда price.value изменится.

Взаимодействие с пропсами и событиями

Получение пропсов

Внутри setup функция получает пропсы через первый аргумент:

export default {
  props: {
    userId: String
  },
  setup(props) {
    console.log(props.userId)
  }
}

// Props — это реактивный объект, можно наблюдать за их изменениями.

Вызов событий

Вторым аргументом в setup поступает функция emit:

export default {
  emits: ['save'],
  setup(props, { emit }) {
    function saveForm() {
      emit('save')
    }
    return { saveForm }
  }
}

// Теперь можете вызывать saveForm внутри шаблона, и событие "save" будет передано наружу.

Переиспользование логики: composables

Одна из главных "фишек" Composition API — возможность переиспользовать логику легко и прозрачно. Выносите повторяющийся код в отдельные функции.

// useCounter.js
import { ref } from 'vue'
export function useCounter() {
  const count = ref(0)
  function increment() {
    count.value++
  }
  return { count, increment }
}

И подключаете их в компонентах:

<script setup>
import { useCounter } from './useCounter'

const { count, increment } = useCounter()
</script>

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

// Здесь вы разбиваете код так, как удобно: бизнес-логику выносите в отдельные файлы и переиспользуете.

Ограничения и "подводные камни" Composition API

  • Отслеживание вложенных изменений: При использовании reactive с массивами и вложенными объектами не забывайте, что Vue отслеживает только существующие свойства. Если добавить новое свойство динамически, оно не станет реактивным.
  • ref внутри объектов: Если создать объект с полями типа ref, обращайтесь к ним всегда через .value или распаковывайте их через toRefs.
  • Типизация: При использовании TypeScript, Composition API работает гораздо удобнее и позволяет явно указывать типы ваших переменных.

Пример: Todo список на Composition API

Давайте соберём простой Todo-компонент с добавлением задач:

<script setup>
// Весь код внутри script setup будет доступен в шаблоне

import { ref } from 'vue'

const todos = ref([
  { text: 'Изучить Vue 3', done: false }
])
const newTodo = ref('')

function addTodo() {
  if (newTodo.value.trim()) {
    todos.value.push({
      text: newTodo.value,
      done: false
    })
    newTodo.value = ''
  }
}

function toggleTodo(index) {
  todos.value[index].done = !todos.value[index].done
}
</script>

<template>
  <div>
    <input v-model="newTodo" placeholder="Новая задача" />
    <button @click="addTodo">Добавить</button>
    <ul>
      <li v-for="(todo, index) in todos" :key="index">
        <label>
          <input type="checkbox" v-model="todo.done" @change="toggleTodo(index)" />
          <span :style="{ textDecoration: todo.done ? 'line-through' : 'none' }">{{ todo.text }}</span>
        </label>
      </li>
    </ul>
  </div>
</template>

// Здесь понятно видно — состояние лежит в ref, вся логика аккуратно размещена внутри setup, подключение в шаблоне выглядит просто.

Взаимодействие с жизненным циклом компонента

Для реакций на жизненный цикл компонента предусмотрены специальные хуки: onMounted, onUpdated, onUnmounted, onBeforeMount и другие.

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

const timer = ref(0)
let intervalId

onMounted(() => {
  intervalId = setInterval(() => {
    timer.value++
  }, 1000)
})

onUnmounted(() => {
  clearInterval(intervalId)
})

// onMounted работает аналогично mounted, но теперь его можно вызывать сколько угодно раз и в любом месте setup либо в composables.

Интеграция с Options API

Многие проекты переходят на Composition API постепенно. Композиционный и опционный API могут сосуществовать в одном проекте и даже в одном компоненте.

export default {
  data() {
    return { a: 10 }
  },
  setup() {
    const b = ref(20)
    return { b }
  }
}

// В шаблоне этого компонента будет доступно и a, и b.

Использование provide и inject

Composition API предоставляет удобный способ делиться состоянием между вложенными компонентами через provide и inject.

// Родительский компонент
import { provide, ref } from 'vue'
export default {
  setup() {
    const color = ref('blue')
    provide('color', color)
  }
}

// Дочерний компонент
import { inject } from 'vue'
export default {
  setup() {
    const color = inject('color')
    // Теперь color можно использовать в шаблоне
    return { color }
  }
}

// Значения, переданные через provide, поддерживают реактивность, если делитесь ref или reactive.

Реализация сложных логик с помощью composables

Рассмотрим реальный пример: создание composable, который будет отслеживать положение мыши.

// 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.clientX
    y.value = event.clientY
  }

  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))
  
  return { x, y }
}
// В компоненте
<script setup>
import { useMouse } from './useMouse'

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

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

// Обратите внимание: состояния и коллбэки полностью изолированы, компонент просто подключает готовую функцию.

Организация больших проектов с помощью Composition API

  • Группируйте бизнес-логику по отдельным composables. Один composable — одна задача (например, форма, запрос к API, работа с локальным состоянием).
  • Используйте отдельные папки для composables. Обычно их кладут в src/composables.
  • Используйте TypeScript для декларации возвращаемых типов и аргументов.

Такой подход поможет вам безболезненно развивать проект, переиспользовать и тестировать логику.


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

Частозадаваемые технические вопросы по теме

Как получить доступ к this внутри setup?

В функции setup не существует контекста this, вместо этого используйте переменные, созданные внутри функции, либо повторно используйте ссылки из composables. Для доступа к свойствам компонента используйте первый аргумент функции (props), события через { emit }, а роутинг или store — импортируйте напрямую.

Как вызвать методы жизненного цикла, если логика вынесена в composable?

Просто импортируйте хук (например, onMounted) из 'vue' и вызывайте его прямо внутри вашего composable. Vue корректно зарегистрирует все обработчики.

// useInit.js
import { onMounted } from 'vue'
export function useInit() {
  onMounted(() => {
    // логика запуска
  })
}

Можно ли использовать v-model с Composition API?

Да, вы можете использовать v-model, если возвращаете переменную из setup. Чтобы настроить v-model на кастомный компонент, используйте emit для события 'update:modelValue', и принимайте 'modelValue' как props.

Как работает provide/inject в Composition API, если значение поменялось?

Если вы передали реактивное значение (ref или reactive), изменения будут автоматически распространяться на все дочерние компоненты, где оно инжектировано.

Как типизировать props, возвращаемые значения и зависимости в Composition API при использовании TypeScript?

Декларируйте типы props через defineProps<[тип]>(), возвращаемые значения setup — через интерфейсы, composables — через явные типы. Используйте тип Ref для ref-переменных:

import { Ref } from 'vue'
const count: Ref<number> = ref(0)
Стрелочка влевоОбработка событий и их передача между компонентами VuejsВзаимодействие с внешними API через Axios в VueСтрелочка вправо

Все гайды по Vue

Работа с пользовательскими интерфейсами и UI библиотеками во VueОрганизация и структура исходных файлов в проектах VueОбзор популярных шаблонов и стартовых проектов на VueКак организовать страницы и маршруты в проекте на VueСоздание серверных приложений на Vue с помощью Nuxt jsРабота со стилями и CSS в Vue js для красивых интерфейсовСоздание и структурирование Vue.js приложенияНастройка и сборка проектов Vue с использованием современных инструментов
Управление переменными и реактивными свойствами во VueИспользование v for и slot в VueТипизация и использование TypeScript в VuejsИспользование шаблонов в Vue js для построения интерфейсовПередача данных между компонентами с помощью props в Vue jsУправление property и функциями во Vue.jsОсновы работы с объектами в VueПонимание жизненного цикла компонента Vue js на примере mountedИспользование метода map в Vue для обработки массивовОбработка пользовательского ввода в Vue.jsОрганизация файлов и структура проекта Vue.jsКомпоненты Vue создание передача данных события и emitИспользование директив и их особенности на Vue с помощью defineСоздание и использование компонентов в Vue JSОбработка кликов и пользовательских событий в Vue
Открыть базу знаний