Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Options API та composition API». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Options API** та **Composition API**: два способи організувати Vue-компоненти. Options використовує іменовані секції об'єкта (`data`, `methods`), Composition групує логіку за фічею через `ref` і `computed` у `setup()`. ```js // Options API export default { data() { return { count: 0 } }, methods: { increment() { this.count++ } } } // Composition API const count = ref(0) const increment = () => count.value++ ``` **Ключове:** Options структурує код за типом; Composition за фічею.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Options API** організовує Vue-компоненти через іменовані властивості об'єкта, наприклад `data` і `methods`. **Composition API** групує логіку за функціональністю через імпортовані функції всередині `setup()`. ## Теорія ### TL;DR - Options API = картотека з підписаними ящиками (дані тут, методи там, computed окремо) - Composition API = дошка зі стікерами, згрупованими за завданням (логіка лічильника в одному блоці) - Головна різниця: Options розкидає пов'язаний код по всьому об'єкту; Composition тримає його разом - Маленький компонент або прототип? Options підійде. Повторне використання логіки або 500+ рядків? Composition - Проект із TypeScript? Composition дає кращий вивід типів через `defineProps<{...}>()` ### Швидкий приклад **Options API** розкидає логіку лічильника по секціях: ```vue <script> export default { data() { return { count: 0 }; }, computed: { double() { return this.count * 2; } }, methods: { increment() { this.count++; } } }; </script> <!-- data, computed і methods у трьох окремих секціях --> <!-- хоча всі вони належать одній фічі - лічильнику --> ``` **Composition API** тримає ту саму логіку разом: ```vue <script setup> import { ref, computed } from 'vue'; const count = ref(0); const double = computed(() => count.value * 2); const increment = () => count.value++; </script> <!-- вся логіка лічильника в одному місці --> ``` Результат ідентичний. Різниця в тому, де шукати пов'язаний код, коли відкриваєш файл. ### Ключова різниця Options API структурує код за **типом**: реактивні дані в `data()`, функції в `methods`, похідні значення в `computed`. Стан, методи і computed лічильника живуть у трьох різних секціях. Composition API структурує за **призначенням**: все що потрібно лічильнику знаходиться в одному блоці. На практиці перехід зазвичай відбувається після третього разу, коли гортаєш файл на 400 рядків у пошуках методу, що змінює конкретний шматок стану. ### Коли використовувати - **Новачок або простий UI-віджет**: Options. Структура передбачувана, не потрібно перебудовувати мислення. - **Повторне використання логіки в кількох компонентах**: Composition. Виносьте в composable на зразок `useCounter()` або `useFetch()`. - **Компонент виростає більше 300-500 рядків**: Composition. Постійно стрибати між `data`, `methods` і `computed` заради однієї фічі стомлює. - **Проект із TypeScript**: Composition. `<script setup>` автоматично виводить типи пропсів через `defineProps<{ name: string }>()`. Options потребує громіздкого злиття декларацій (declaration merging) через `ComponentOptions`. - **Vue 2 або міграція**: Options. Пряма сумісність без зайвих пакетів. - **Pinia або Nuxt 3 composables**: Composition. Обидва побудовані навколо цієї моделі. ### Таблиця порівняння | Аспект | Options API | Composition API | |---|---|---| | **Структура** | За типом опції (data/methods/computed) | За фічею (логіка лічильника разом) | | **Реактивність** | `this.count` (відстежується автоматично) | `ref(0)` або `reactive({})`, потрібен `.value` | | **Повторне використання** | Копіювати методи по компонентах вручну | Composables (`useCounter()`, `useFetch()`) | | **TypeScript** | Громіздкі `ComponentOptions` декларації | Вивід типів у `<script setup>` | | **Розмір файлу** | Компактний для малих компонентів | Добре масштабується для великих файлів | | **Коли використовувати** | Прототипи, Vue 2, прості віджети | Продакшн, спільна логіка, TypeScript | ### Як це працює всередині Vue 3 парсить Options API в proxy-обернутий контекст `this` і автоматично прив'язує реактивність через `Object.defineProperty` на властивості `data` під час ініціалізації компонента. Composition API запускає `setup()` першим, збираючи проксі `ref` і `reactive` у спільний контекст рендеру. Планувальник залежностей під час компіляції шаблону працює однаково в обох випадках. Структурна різниця в тому, що логіка Composition залишається в ланцюжках областей видимості JavaScript, а не висить на `this`. ### Типові помилки **Мутація `ref` без `.value`:** ```js const count = ref(0); count++; // залишається 0, збільшено об'єкт-обгортку, а не значення ``` `ref` повертає `{ value: 0 }`. Пряма мутація обходить реактивність. Правило: `count.value++`. **Звернення до `this` всередині `setup()`:** ```js export default { setup() { console.log(this.count); // undefined } } ``` `setup()` запускається до того, як proxy `this` існує. Немає до чого прив'язуватися. Повертайте refs зі `setup`, або використовуйте `<script setup>`. **Припущення, що `reactive` відстежує глибоко вкладені додавання:** ```js const state = reactive({ items: [] }); state.items.push({ count: 0 }); // { count: 0 } НЕ є реактивним ``` `reactive` обгортає тільки об'єкт верхнього рівня. Об'єкти, що додаються пізніше, потребують власного `ref` або `reactive`. Правило: `state.items.push({ count: ref(0) })`. **Змішування обох API в одному компоненті:** ```js export default { data() { return { x: 0 }; }, setup() { return { x: ref(0) }; } // Options x перекриває setup x } ``` Затінення. Властивості Options перекривають повернуті зі `setup` значення з однаковим ім'ям. Обирайте одне API на компонент. ### Де зустрічається - **Nuxt 3**: Composition скрізь. `useFetch`, `useRoute`, `useAsyncData` - усе composables. - **Pinia**: `defineStore` використовує Composition-стиль із `ref` і `computed` всередині. - **VitePress**: `<script setup>` у всіх інтерактивних demo-прикладах документації. - **Element Plus**: Складна логіка мігрує на Composition для підтримки tree-shaking. - **Vuetify**: Options для базових компонентів, Composition для кастомної логіки плагінів. ### Follow-up питання **Q:** Що таке composable? **A:** Функція, яка всередині використовує Composition API і повертає реактивний стан або методи. Приклад: `function useCounter() { const count = ref(0); return { count, increment: () => count.value++ }; }`. Аналог кастомних хуків у React. **Q:** Яка різниця між `ref` і `reactive`? **A:** `ref` працює для примітивів і об'єктів, у шаблонах розпаковується автоматично, але в скрипті потрібен `.value`. `reactive` тільки для об'єктів, дозволяє прямий доступ без `.value`, але втрачає реактивність при деструктуризації. `ref` для `count`, `reactive` для `{ user, settings }`. **Q:** Чи можна використовувати Options API у Vue 3? **A:** Так. Options API повністю підтримується у Vue 3, команда Vue не планує його прибирати. Composition API - доповнення, а не заміна. **Q:** Чому Composition API краще для TypeScript? **A:** `<script setup>` із `defineProps<{ name: string }>()` виводить типи безпосередньо з дженерика. Options потребує громіздкого злиття декларацій через `ComponentOptions`. Тому TypeScript-команди зазвичай обирають Composition. **Q:** Переведіть цей Options-компонент на Composition. (Senior) **A:** Властивості `data` стають `ref` або `reactive`. Властивості `computed` стають викликами `computed()`. Методи стають звичайними функціями. Хуки на зразок `mounted` стають `onMounted`. Використовуйте `<script setup>`, щоб уникнути явного return. ## Приклади ### Базовий лічильник: Options проти Composition Options API розбиває лічильник по трьох секціях: ```vue <template> <p>Counter: {{ count }} | Double: {{ double }}</p> <button @click="increment">+</button> </template> <script> export default { data() { return { count: 0 }; }, computed: { double() { return this.count * 2; } }, methods: { increment() { this.count++; } } }; </script> ``` Composition API тримає ту саму логіку разом: ```vue <template> <p>Counter: {{ count }} | Double: {{ double }}</p> <button @click="increment">+</button> </template> <script setup> import { ref, computed } from 'vue'; const count = ref(0); const double = computed(() => count.value * 2); const increment = () => count.value++; </script> ``` Рендер ідентичний. У Options-версії для розуміння лічильника потрібно читати три окремих секції. У Composition - чотири послідовних рядки. ### Список задач із фільтрацією Ось де різниця стає відчутною. Options розкидає логіку фільтрації по файлу: ```vue <script> export default { data() { return { todos: [], filter: 'all' // стан тут }; }, computed: { filteredTodos() { // похідна логіка далеко від стану return this.todos.filter(t => this.filter === 'all' || t.done === (this.filter === 'done') ); } }, methods: { addTodo(text) { // мутації ще далі this.todos.push({ text, done: false }); } } }; </script> ``` Composition тримає все разом і спрощує вилучення: ```vue <script setup> import { ref, computed } from 'vue'; // Вся логіка задач в одному блоці, легко перенести в useTodos.js const todos = ref([]); const filter = ref('all'); const filteredTodos = computed(() => todos.value.filter(t => filter.value === 'all' || t.done === (filter.value === 'done') ) ); const addTodo = (text) => todos.value.push({ text, done: false }); </script> ``` Потрібно поділитися логікою з іншим компонентом? Берете блок і переносите в `useTodos.js`. З Options доведеться копіювати з трьох різних місць. ### Патерн composable Справжня цінність у продакшн. Виносимо в `useTodos.js`: ```js // useTodos.js import { ref, computed } from 'vue'; export function useTodos() { const todos = ref([]); const filter = ref('all'); const filteredTodos = computed(() => todos.value.filter(t => filter.value === 'all' || t.done === (filter.value === 'done') ) ); const addTodo = (text) => todos.value.push({ text, done: false }); return { todos, filter, filteredTodos, addTodo }; } ``` Будь-який компонент імпортує це двома рядками: ```vue <script setup> import { useTodos } from './useTodos'; const { todos, filter, filteredTodos, addTodo } = useTodos(); </script> ``` У Options API немає рівнозначного патерну. Міксини (mixins) існували, але проблеми з колізією імен і непрозорими джерелами даних робили їх болючими. Composables замінили їх повністю.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.