4. Options API
Options API — классика Vue 🎓
Заголовок раздела «Options API — классика Vue 🎓»Прежде чем перейти к Composition API полностью, важно понять Options API. Почему? Потому что огромное количество существующего Vue кода написано на Options API, и тебе придётся его читать и поддерживать. Плюс, для небольших компонентов Options API всё ещё отлично работает! Не выбрасывай старый инструмент — просто знай, когда его применять. 🔧
Анатомия компонента Options API 🏗️
Заголовок раздела «Анатомия компонента Options API 🏗️»<script lang="ts">import { defineComponent } from 'vue'import type { PropType } from 'vue'
interface User { id: number name: string}
export default defineComponent({ name: 'UserDashboard', // имя компонента (для DevTools)
// Дочерние компоненты components: { UserCard, UserList },
// Директивы directives: { focus: { mounted: el => el.focus() } },
// Props — входные данные props: { initialCount: { type: Number, default: 0 }, user: { type: Object as PropType<User>, required: true } },
// Events — исходящие события emits: ['update', 'delete'],
// Реактивные данные data() { return { count: this.initialCount, // можно использовать this.props! message: 'Привет', items: [] as string[] } },
// Вычисляемые свойства (кешируются!) computed: { doubled(): number { return this.count * 2 }, fullMessage(): string { return `${this.message}: ${this.count}` } },
// Наблюдатели за изменениями watch: { count(newVal: number, oldVal: number) { console.log(`count: ${oldVal} → ${newVal}`) } },
// Методы methods: { increment() { this.count++ this.$emit('update', this.count) }, async loadData() { this.items = await fetchItems() } },
// Lifecycle hooks created() { /* Компонент создан, DOM ещё нет */ }, mounted() { this.loadData() }, updated() { /* DOM обновлён */ }, unmounted() { /* Компонент удалён */ }})</script>data() — реактивные данные 📊
Заголовок раздела «data() — реактивные данные 📊»export default defineComponent({ data() { // ВАЖНО: возвращать новый объект для каждого экземпляра! return { // Примитивы count: 0, message: '', isVisible: true,
// Объекты user: { name: 'Яша', age: 25 },
// Массивы items: ['Vue', 'React', 'Angular'],
// null для async данных currentUser: null as User | null,
// Данные из props (в data() доступен this!) localCount: this.initialCount } }})Важное правило: данные в data() должны быть объявлены сразу — добавление свойств позже не работает в реактивном смысле:
methods: { // ❌ Не реактивно! Vue не отслеживает новые свойства addNewField() { this.newField = 'значение' // шаблон не обновится! },
// ✅ Правильно — использовать Object.assign или деструктуризацию updateUser() { this.user = { ...this.user, newField: 'значение' } }}computed — вычисляемые свойства 🧮
Заголовок раздела «computed — вычисляемые свойства 🧮»export default defineComponent({ data() { return { firstName: 'Яша', lastName: 'Смирнов', items: [ { id: 1, name: 'Vue', price: 0 }, { id: 2, name: 'Vite', price: 0 }, { id: 3, name: 'Pinia', price: 0 } ], filterText: '' } },
computed: { // Простое вычисление — только getter fullName(): string { return `${this.firstName} ${this.lastName}` },
// Вычисление массива filteredItems() { return this.items.filter(item => item.name.toLowerCase().includes(this.filterText.toLowerCase()) ) },
// Computed с getter И setter name: { get(): string { return `${this.firstName} ${this.lastName}` }, set(value: string) { const parts = value.split(' ') this.firstName = parts[0] || '' this.lastName = parts[1] || '' } } },
methods: { updateName() { // Вызов setter computed this.name = 'Иван Петров' // Теперь: firstName = 'Иван', lastName = 'Петров' } }})Computed кешируются! Это ключевое отличие от methods:
computed: { // ✅ Перевычисляется только когда expensiveList изменится processedList() { return this.expensiveList.map(item => heavyCalculation(item)) }},methods: { // ❌ Вызывается каждый раз при рендере! getProcessedList() { return this.expensiveList.map(item => heavyCalculation(item)) }}watch — наблюдатели 👀
Заголовок раздела «watch — наблюдатели 👀»export default defineComponent({ data() { return { searchQuery: '', user: { name: 'Яша', settings: { theme: 'dark' } }, routeId: 1 } },
watch: { // Простой watcher searchQuery(newVal: string, oldVal: string) { console.log(`Поиск: "${oldVal}" → "${newVal}"`) this.fetchResults(newVal) },
// Watcher с опциями routeId: { immediate: true, // вызвать сразу при монтировании handler(newId: number) { this.fetchUserById(newId) } },
// Глубокий watcher для объектов user: { deep: true, // отслеживает вложенные изменения handler(newUser: User) { this.saveToStorage(newUser) } },
// Watcher на вложенное свойство 'user.settings.theme'(newTheme: string) { document.body.className = newTheme } }})Lifecycle hooks в Options API 🔄
Заголовок раздела «Lifecycle hooks в Options API 🔄»export default defineComponent({ // ─── ФАЗА СОЗДАНИЯ ────────────────────────── beforeCreate() { // Самый ранний hook. data и computed НЕ доступны // Используется редко (Composition API setup() = beforeCreate + created) console.log('beforeCreate: this.$data =', this.$data) // undefined },
created() { // data, computed, methods — ДОСТУПНЫ // DOM ещё НЕ создан (this.$el = undefined) // Идеально для fetch начальных данных this.fetchInitialData() },
// ─── ФАЗА МОНТИРОВАНИЯ ────────────────────── beforeMount() { // DOM создаётся, но ещё не вставлен в документ // Используется очень редко },
mounted() { // ✅ DOM доступен! this.$el — HTML элемент // Идеально для: DOM манипуляций, third-party библиотек, fetch this.$el.querySelector('input')?.focus()
// Работа с реальным DOM через $refs this.$refs.canvas.getContext('2d')
// Инициализация внешних библиотек this.chart = new Chart(this.$refs.canvas, { ... }) },
// ─── ФАЗА ОБНОВЛЕНИЯ ──────────────────────── beforeUpdate() { // Данные изменились, DOM ещё НЕ обновлён // Можно получить старый DOM console.log('DOM до обновления:', this.$el.innerHTML) },
updated() { // DOM обновлён! // ⚠️ Не меняй данные здесь — бесконечный цикл! console.log('DOM после обновления:', this.$el.innerHTML) },
// ─── ФАЗА УДАЛЕНИЯ ────────────────────────── beforeUnmount() { // Компонент ещё живёт, но скоро будет удалён // Cleanup: отписки, таймеры, внешние библиотеки clearInterval(this.timer) this.chart?.destroy() window.removeEventListener('resize', this.handleResize) },
unmounted() { // Компонент удалён из DOM console.log('Компонент удалён') },
// ─── СПЕЦИАЛЬНЫЕ HOOKS ────────────────────── activated() { // При активации <KeepAlive> компонента this.refreshData() },
deactivated() { // При деактивации <KeepAlive> компонента },
errorCaptured(err, instance, info) { // Перехватывает ошибки из дочерних компонентов console.error(err) return false // предотвращает дальнейшее распространение ошибки }})Mixins — старый способ переиспользования 🧪
Заголовок раздела «Mixins — старый способ переиспользования 🧪»Mixins — это способ переиспользовать Options API логику. В Vue 3 их заменили composables, но ты встретишь mixins в legacy коде:
export const timestampMixin = { data() { return { createdAt: new Date(), updatedAt: new Date() } }, methods: { updateTimestamp() { this.updatedAt = new Date() } }}
// Использованиеexport default defineComponent({ mixins: [timestampMixin],
data() { return { name: 'Яша' } },
methods: { save() { this.updateTimestamp() // метод из mixin! // сохраняем... } }})Проблемы mixins:
// ❌ Конфликт имён — непредсказуемо!const mixin1 = { data() { return { value: 1 } } }const mixin2 = { data() { return { value: 2 } } }
// Какой value победит? Зависит от порядка! 😱defineComponent({ mixins: [mixin1, mixin2] })
// ❌ Неявный источник данных// Откуда взялся this.someMethod()? Из mixin1? mixin2? Самого компонента?// Невозможно понять без анализа всех mixins!
// ✅ Composables решают это явноconst { value: value1 } = useMixin1() // понятно откуда!const { value: value2 } = useMixin2() // понятно откуда!Когда ещё использовать Options API? 📋
Заголовок раздела «Когда ещё использовать Options API? 📋»✅ Options API подходит для:├── Простые компоненты (формы, карточки, кнопки)├── Команда, переходящая с Vue 2├── Быстрое прототипирование└── Компоненты без сложной переиспользуемой логики
❌ Composition API лучше для:├── Сложная бизнес-логика├── Переиспользуемые composables├── TypeScript строгая типизация└── Крупные команды и проектыТаблица сравнения Options API vs Composition API 📊
Заголовок раздела «Таблица сравнения Options API vs Composition API 📊»| Options API | Composition API | Назначение |
|---|---|---|
data() | ref(), reactive() | Реактивные данные |
computed | computed() | Вычисляемые свойства |
watch | watch(), watchEffect() | Наблюдатели |
methods | Обычные функции | Методы |
created | setup() (сразу) | Инициализация |
mounted | onMounted() | После монтирования |
updated | onUpdated() | После обновления |
unmounted | onUnmounted() | После удаления |
props | defineProps<T>() | Входные данные |
emits | defineEmits<T>() | Исходящие события |
mixins | composables | Переиспользование |
this.$refs | ref() для DOM | Доступ к DOM |
provide/inject | provide(), inject() | DI |
Доступ к экземпляру компонента через this ⚙️
Заголовок раздела «Доступ к экземпляру компонента через this ⚙️»В Options API this — это экземпляр компонента с множеством встроенных свойств:
mounted() { this.$el // корневой DOM элемент this.$refs // template refs this.$attrs // нереактивные атрибуты (fallthrough) this.$slots // слоты this.$emit // метод emit this.$parent // родительский компонент (избегать!) this.$root // корневой компонент this.$router // Vue Router (если установлен) this.$store // Vuex (если установлен) this.$nextTick(() => { /* после обновления DOM */ }) this.$forceUpdate() // принудительное обновление (избегать!)}Резюме 🎓
Заголовок раздела «Резюме 🎓»Options API — это:
data()— реактивные данные (объект из фабрики)computed— кешированные вычисленияwatch— наблюдатели сdeep,immediatemethods— функции компонента- Lifecycle hooks —
mounted,unmountedи другие - Mixins — старый способ переиспользования (заменён composables)
Options API жив и поддерживается в Vue 3. Но для новых проектов — выбирай Composition API! ✅