
Vue.js 3: Composition API
Composition API — это новый способ организации логики компонентов в Vue.js 3, который предоставляет более гибкий и мощный подход к написанию кода по сравнению с традиционным Options API. Composition API позволяет лучше организовать код, переиспользовать логику между компонентами и создавать более читаемые и поддерживаемые приложения.
1. Зачем нужен Composition API?
Composition API решает несколько ключевых проблем Options API:
- Лучшая организация логики — связанная функциональность группируется вместе, а не разбрасывается по разным секциям
- Переиспользование логики — легко создавать и переиспользовать логику между компонентами
- Лучшая типизация — полная поддержка TypeScript из коробки
- Древовидная структура — логика может быть организована в древовидную структуру для лучшей читаемости
- Лучшая поддержка IDE — более точное автодополнение и навигация по коду
Composition API особенно полезен для больших компонентов и сложной логики, где Options API может стать трудным для понимания.
2. Основы Composition API
2.1. setup() функция
Основная точка входа в Composition API — функция setup()
:
<template>
<div>
<h1>{{ title }}</h1>
<p>Счётчик: {{ count }}</p>
<button @click="increment">Увеличить</button>
</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
name: 'CounterComponent',
setup() {
// Реактивные данные
const count = ref(0)
const title = ref('Счётчик')
// Методы
const increment = () => {
count.value++
}
// Хуки жизненного цикла
onMounted(() => {
console.log('Компонент смонтирован')
})
// Возвращаем всё, что нужно в template
return {
count,
title,
increment
}
}
}
</script>
Ключевые моменты:
setup()
выполняется до создания экземпляра компонента- Все переменные и функции должны быть возвращены для использования в template
ref()
создаёт реактивные ссылки на данные
2.2. Реактивные ссылки с ref()
ref()
создаёт реактивную ссылку на значение:
import { ref } from 'vue'
export default {
setup() {
// Примитивные значения
const count = ref(0)
const name = ref('Иван')
const isActive = ref(false)
// Объекты
const user = ref({
name: 'Иван',
age: 25,
email: 'ivan@example.com'
})
// Массивы
const items = ref(['item1', 'item2', 'item3'])
// Доступ к значению через .value
console.log(count.value) // 0
count.value = 5
console.log(count.value) // 5
// В template .value не нужен
return {
count,
name,
isActive,
user,
items
}
}
}
2.3. Реактивные объекты с reactive()
reactive()
создаёт реактивный объект:
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({
count: 0,
name: 'Иван',
user: {
age: 25,
email: 'ivan@example.com'
},
items: ['item1', 'item2']
})
// Изменения автоматически реактивны
state.count = 5
state.user.age = 26
state.items.push('item4')
return {
state
}
}
}
Важно: reactive()
работает только с объектами, а ref()
работает с любыми типами данных.
3. Хуки жизненного цикла
Composition API предоставляет хуки жизненного цикла, которые заменяют Options API:
import {
onMounted,
onUnmounted,
onUpdated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate
} from 'vue'
export default {
setup() {
onBeforeMount(() => {
console.log('Компонент будет смонтирован')
})
onMounted(() => {
console.log('Компонент смонтирован')
// Инициализация, API вызовы, подписки
})
onBeforeUpdate(() => {
console.log('Компонент будет обновлён')
})
onUpdated(() => {
console.log('Компонент обновлён')
})
onBeforeUnmount(() => {
console.log('Компонент будет размонтирован')
})
onUnmounted(() => {
console.log('Компонент размонтирован')
// Очистка, отписки
})
}
}
4. Вычисляемые свойства
4.1. computed() для вычисляемых свойств
import { ref, computed } from 'vue'
export default {
setup() {
const firstName = ref('Иван')
const lastName = ref('Иванов')
const age = ref(25)
// Простое вычисляемое свойство
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// Вычисляемое свойство с зависимостями
const isAdult = computed(() => {
return age.value >= 18
})
// Вычисляемое свойство с геттером и сеттером
const fullNameWithSetter = computed({
get: () => `${firstName.value} ${lastName.value}`,
set: (value) => {
const [first, last] = value.split(' ')
firstName.value = first
lastName.value = last
}
})
return {
firstName,
lastName,
age,
fullName,
isAdult,
fullNameWithSetter
}
}
}
5. Наблюдатели
5.1. watch() для наблюдения за изменениями
import { ref, watch, watchEffect } from 'vue'
export default {
setup() {
const count = ref(0)
const name = ref('Иван')
const user = ref({ age: 25 })
// Простое наблюдение
watch(count, (newValue, oldValue) => {
console.log(`Счётчик изменился с ${oldValue} на ${newValue}`)
})
// Наблюдение с опциями
watch(name, (newValue, oldValue) => {
console.log(`Имя изменилось с "${oldValue}" на "${newValue}"`)
}, {
immediate: true, // Выполнить сразу при создании
deep: false // Не следить за вложенными свойствами
})
// Глубокое наблюдение за объектом
watch(user, (newValue, oldValue) => {
console.log('Пользователь изменился:', newValue)
}, { deep: true })
// Наблюдение за несколькими источниками
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
console.log(`Счётчик: ${oldCount} → ${newCount}`)
console.log(`Имя: "${oldName}" → "${newName}"`)
})
// watchEffect - автоматически отслеживает зависимости
watchEffect(() => {
console.log(`Счётчик: ${count.value}, Имя: ${name.value}`)
})
return {
count,
name,
user
}
}
}
6. Композиция логики
6.1. Создание переиспользуемых композиций
Одна из главных возможностей Composition API — создание переиспользуемых композиций:
// composables/useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
const doubleCount = computed(() => count.value * 2)
const isEven = computed(() => count.value % 2 === 0)
return {
count,
increment,
decrement,
reset,
doubleCount,
isEven
}
}
// composables/useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, defaultValue) {
const storedValue = localStorage.getItem(key)
const value = ref(storedValue ? JSON.parse(storedValue) : defaultValue)
watch(value, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
})
return value
}
// composables/useApi.js
import { ref, onMounted } from 'vue'
export function useApi(url) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const fetchData = async () => {
loading.value = true
error.value = null
try {
const response = await fetch(url)
data.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
onMounted(fetchData)
return {
data,
loading,
error,
fetchData
}
}
6.2. Использование композиций в компонентах
<template>
<div>
<!-- Счётчик -->
<div class="counter">
<h3>Счётчик: {{ count }}</h3>
<p>Удвоенное значение: {{ doubleCount }}</p>
<p>Чётное: {{ isEven ? 'Да' : 'Нет' }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">Сброс</button>
</div>
<!-- Пользователь -->
<div class="user">
<h3>Пользователь</h3>
<input v-model="userName" placeholder="Введите имя" />
<p>Сохранённое имя: {{ userName }}</p>
</div>
<!-- API данные -->
<div class="api-data">
<h3>Данные с API</h3>
<div v-if="loading">Загрузка...</div>
<div v-else-if="error">Ошибка: {{ error }}</div>
<div v-else>
<pre>{{ JSON.stringify(data, null, 2) }}</pre>
<button @click="fetchData">Обновить</button>
</div>
</div>
</div>
</template>
<script>
import { useCounter } from '@/composables/useCounter'
import { useLocalStorage } from '@/composables/useLocalStorage'
import { useApi } from '@/composables/useApi'
export default {
name: 'CompositionExample',
setup() {
// Используем композиции
const { count, increment, decrement, reset, doubleCount, isEven } = useCounter(10)
const userName = useLocalStorage('userName', 'Гость')
const { data, loading, error, fetchData } = useApi('https://api.example.com/data')
return {
count,
increment,
decrement,
reset,
doubleCount,
isEven,
userName,
data,
loading,
error,
fetchData
}
}
}
</script>
7. Работа с props и emit
7.1. Получение props
import { toRefs } from 'vue'
export default {
props: {
title: String,
count: Number,
user: Object
},
setup(props) {
// Деструктурируем props с сохранением реактивности
const { title, count, user } = toRefs(props)
// Теперь title, count, user - реактивные ссылки
console.log(title.value) // значение title
console.log(count.value) // значение count
return {
title,
count,
user
}
}
}
7.2. Отправка событий
export default {
setup(props, { emit }) {
const increment = () => {
emit('increment', 1)
}
const updateUser = (userData) => {
emit('update:user', userData)
}
return {
increment,
updateUser
}
}
}
8. TypeScript поддержка
Composition API отлично работает с TypeScript:
import { ref, computed, Ref } from 'vue'
interface User {
id: number
name: string
email: string
age: number
}
interface CounterState {
count: Ref<number>
increment: () => void
decrement: () => void
reset: () => void
}
export function useCounter(initialValue: number = 0): CounterState {
const count = ref<number>(initialValue)
const increment = (): void => {
count.value++
}
const decrement = (): void => {
count.value--
}
const reset = (): void => {
count.value = initialValue
}
return {
count,
increment,
decrement,
reset
}
}
export function useUser(): {
user: Ref<User | null>
setUser: (user: User) => void
clearUser: () => void
} {
const user = ref<User | null>(null)
const setUser = (newUser: User): void => {
user.value = newUser
}
const clearUser = (): void => {
user.value = null
}
return {
user,
setUser,
clearUser
}
}
9. Лучшие практики
9.1. Организация кода
export default {
setup() {
// 1. Реактивные данные в начале
const count = ref(0)
const name = ref('')
const user = ref(null)
// 2. Вычисляемые свойства
const fullName = computed(() => `${name.value} ${user.value?.lastName || ''}`)
const isAdult = computed(() => user.value?.age >= 18)
// 3. Методы
const increment = () => count.value++
const updateUser = (newUser) => user.value = newUser
// 4. Хуки жизненного цикла
onMounted(() => {
// инициализация
})
// 5. Возврат всего необходимого
return {
count,
name,
user,
fullName,
isAdult,
increment,
updateUser
}
}
}
9.2. Именование композиций
// ✅ Хорошо - начинается с "use"
export function useCounter() { }
export function useLocalStorage() { }
export function useApi() { }
// ❌ Плохо - не начинается с "use"
export function counter() { }
export function localStorage() { }
export function api() { }
9.3. Избегание антипаттернов
// ❌ Плохо - создание реактивных данных в циклах
export default {
setup() {
const items = []
for (let i = 0; i < 1000; i++) {
items.push(ref(i)) // Создаёт много реактивных ссылок
}
}
}
// ✅ Хорошо - создание массива и реактивной ссылки на него
export default {
setup() {
const items = ref(Array.from({ length: 1000 }, (_, i) => i))
}
}
10. Миграция с Options API
10.1. Постепенная миграция
Vue 3 позволяет использовать Composition API и Options API в одном компоненте:
<template>
<div>
<h1>{{ title }}</h1>
<p>Счётчик: {{ count }}</p>
<button @click="increment">Увеличить</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'HybridComponent',
// Options API
data() {
return {
oldCount: 0
}
},
methods: {
oldIncrement() {
this.oldCount++
}
},
// Composition API
setup() {
const count = ref(0)
const title = ref('Гибридный компонент')
const increment = () => {
count.value++
}
return {
count,
title,
increment
}
}
}
</script>
10.2. Полная миграция
// До (Options API)
export default {
data() {
return {
count: 0,
name: 'Иван'
}
},
computed: {
fullName() {
return `${this.name} Иванов`
}
},
methods: {
increment() {
this.count++
}
},
mounted() {
console.log('Компонент смонтирован')
}
}
// После (Composition API)
import { ref, computed, onMounted } from 'vue'
export default {
setup() {
const count = ref(0)
const name = ref('Иван')
const fullName = computed(() => `${name.value} Иванов`)
const increment = () => {
count.value++
}
onMounted(() => {
console.log('Компонент смонтирован')
})
return {
count,
name,
fullName,
increment
}
}
}
Заключение
Composition API в Vue.js 3 предоставляет мощный и гибкий способ организации логики компонентов. Основные преимущества:
- Лучшая организация кода — связанная функциональность группируется вместе
- Переиспользование логики — легко создавать и переиспользовать композиции
- Полная поддержка TypeScript — лучшая типизация и поддержка IDE
- Гибкость — возможность комбинировать с Options API
- Производительность — оптимизированная реактивность
Composition API особенно полезен для:
- Больших и сложных компонентов
- Переиспользуемой логики между компонентами
- Проектов с TypeScript
- Команд, которые хотят улучшить читаемость и поддерживаемость кода
Начните с простых компонентов и постепенно переходите к более сложным композициям. Помните о лучших практиках и избегайте антипаттернов для создания качественного и поддерживаемого кода.