Skip to main content

Options API та composition API

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 APIComposition 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 замінили їх повністю.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?