Vue 3 Composition API vs Options API: миграция и паттерны
Composition API — главное архитектурное изменение Vue 3. Он не заменяет Options API полностью, но решает проблемы, с которыми Options API не справлялся: переиспользование логики, типизация и организация кода в больших компонентах. Разберём оба подхода, покажем паттерны миграции и определим, когда какой API уместен.
Зачем Composition API: проблема mixins и логического разделения
В Options API логика разбита по опциям: data, methods, computed, watch. Когда компонент растёт, связанная логика разбросана по файлу. Фича «поиск» — переменная в data, метод в methods, вотчер в watch. Всё в разных местах.
Mixins решали переиспользование, но создавали новые проблемы:
- Конфликты имён — два миксина с одинаковым свойством молча перезаписывают друг друга
- Неявные зависимости — непонятно, откуда пришло
this.loading - Плохая типизация — TypeScript не выводит типы из миксинов
Composition API решает все три: логика группируется по фичам, зависимости явные, TypeScript выводит типы автоматически.
setup() vs <script setup>
<!-- setup() внутри defineComponent -->
<script lang="ts">
import { ref, computed, defineComponent } from 'vue';
export default defineComponent({
setup() {
const count = ref(0);
const doubled = computed(() => count.value * 2);
const increment = () => { count.value++ };
return { count, doubled, increment };
},
});
</script>
<!-- <script setup> — рекомендуемый способ -->
<script setup lang="ts">
import { ref, computed } from 'vue';
const count = ref(0);
const doubled = computed(() => count.value * 2);
const increment = () => { count.value++ };
</script>
<template>
<button @click="increment">{{ count }} × 2 = {{ doubled }}</button>
</template>
<script setup> — синтаксический сахар. Всё на верхнем уровне автоматически доступно в шаблоне. Не нужен return, не нужен defineComponent. Меньше boilerplate, лучше tree-shaking.
Реактивность: ref vs reactive, computed, watch
<script setup lang="ts">
import { ref, reactive, computed, watch, watchEffect } from 'vue';
// ref — для примитивов и любых значений
const name = ref<string>('');
const count = ref(0);
// reactive — для объектов (без .value, но нельзя переприсвоить)
const form = reactive({
email: '',
password: '',
remember: false,
});
// computed — вычисляемое с кешированием
const isValid = computed(() =>
form.email.includes('@') && form.password.length >= 8
);
// watch — реакция на изменения
watch(() => form.email, (newVal, oldVal) => {
console.log(`Email: ${oldVal} → ${newVal}`);
});
// watchEffect — автотрекинг зависимостей
watchEffect(() => {
console.log(`Count: ${count.value}`);
});
</script>
Правило: используй ref по умолчанию. reactive — только для объектов-форм, где удобнее без .value. Не смешивай подходы в одном компоненте.
Composables: переиспользуемая логика
Composable — функция, инкапсулирующая реактивную логику. Замена mixins без их проблем:
<!-- composables/useAuth.ts -->
<script lang="ts">
import { ref, computed } from 'vue';
interface User { id: string; name: string; role: string }
const user = ref<User | null>(null);
export function useAuth() {
const isAuthenticated = computed(() => user.value !== null);
async function login(email: string, password: string) {
const res = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
user.value = await res.json();
}
function logout() { user.value = null; }
return { user, isAuthenticated, login, logout };
}
</script>
<!-- Использование -->
<script setup lang="ts">
import { useAuth } from '@/composables/useAuth';
const { user, isAuthenticated, logout } = useAuth();
</script>
<template>
<div v-if="isAuthenticated">
<span>{{ user?.name }}</span>
<button @click="logout">Выйти</button>
</div>
</template>
Преимущества: явный импорт, нет конфликтов имён, полная типизация, легко тестировать изолированно.
Миграция с Options API: пошаговый подход
Vue 3 поддерживает оба API одновременно — мигрируйте постепенно:
- Новые компоненты — пишите на
<script setup>. Старые не трогайте. - Извлекайте mixins в composables — один mixin = один composable.
- Мигрируйте по одному — начинайте с листовых компонентов.
data→ref,computed→computed(),methods→ функции,watch→watch(). - Lifecycle —
mounted→onMounted(),beforeUnmount→onBeforeUnmount(). - Удаляйте this — в Composition API нет
this. Все зависимости через замыкания.
Когда Options API всё ещё ок
- Простые компоненты — кнопка, карточка, модалка с 2-3 пропсами и минимальной логикой
- Команда новичков — Options API проще для входа: структура фиксирована
- Легаси без планов на рефакторинг — если код работает и не растёт, миграция не оправдана
- Прототипы — когда скорость важнее архитектуры
Для любого нового проекта на Vue 3 рекомендация однозначная: <script setup> + Composition API. Это стандарт экосистемы, лучше типизируется и масштабируется на большие кодовые базы. Options API никуда не денется — но это legacy-режим, а не путь вперёд.