Vue Front-end Инженер

Vue Front-end Инженер

Роадмап навыков для прокачки

Основы Composition API во Vue 3

VueJSComponentsComposition API

Основная идея

Composition API — это альтернативный способ описания логики компонентов во Vue 3. Вместо разделения по опциям (data, methods, computed) код организуется по логическим функциям, что улучшает переиспользование и читаемость.

Ключевые аспекты

  • Функциональный подход — логика компонента описывается в функции setup или <script setup>
  • Реактивность через функции — ref, reactive, computed вместо опций data и computed
  • Composables — переиспользуемые функции с логикой (аналог mixins, но лучше)
  • Лучшая типизация — TypeScript работает естественнее
  • Гибкая организация — код группируется по фичам, а не по типу

Плюсы

  • Лучшее переиспользование логики через composables
  • Проще читать большие компоненты
  • Отличная поддержка TypeScript
  • Меньше магии — явные импорты и зависимости

Минусы

  • Более высокий порог входа для новичков
  • Больше boilerplate для простых компонентов
  • Нужно понимать реактивность Vue

Частые ошибки на собеседованиях

  • Не понимают зачем нужен Composition API если есть Options API
  • Путают setup() функцию и <script setup> синтаксис
  • Не знают про composables и как их создавать
  • Думают, что Composition API заменяет Options API (они могут сосуществовать)

Введение и проблематика

В Options API код организован по типу: все данные в data, все методы в methods, все вычисляемые свойства в computed. При росте компонента связанная логика оказывается разбросана по разным секциям.

Composition API решает эту проблему: код организуется по логическим функциям, а не по типу опций.

Composition API — это не замена Options API, а альтернативный способ описания логики. Оба подхода можно использовать в одном проекте.


Базовая теория

Options API vs Composition API

Code Example 1: Чем отличаются эти два варианта реализации счётчика? Какой подход использует Options API, а какой — Composition API?

vue
<template>
  <p>{{ count }}</p>
  <button @click="increment">+1</button>
</template>
 
<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

Практические примеры

Базовый компонент

Code Example 2: Что делает этот компонент? Какие возможности Composition API здесь используются для работы с данными и вычислениями?

vue
<template>
  <div>
    <input v-model="searchQuery" placeholder="Поиск..." />
    <ul>
      <li v-for="item in filteredItems" :key="item.id">
        {{ item.name }}
      </li>
    </ul>
    <p>Найдено: {{ itemCount }}</p>
  </div>
</template>
 
<script setup>
import { ref, computed } from 'vue'
 
// Реактивные данные
const searchQuery = ref('')
const items = ref([
  { id: 1, name: 'Vue.js' },
  { id: 2, name: 'React' },
  { id: 3, name: 'Angular' }
])
 
// Вычисляемые свойства
const filteredItems = computed(() => {
  const query = searchQuery.value.toLowerCase()
  return items.value.filter(item =>
    item.name.toLowerCase().includes(query)
  )
})
 
const itemCount = computed(() => filteredItems.value.length)
</script>

Организация по фичам

Code Example 3: Как организован код в Options API и в Composition API? Какой подход лучше масштабируется при росте компонента и почему?

В Options API логика разбросана:

vue
<script>
export default {
  data() {
    return {
      // Данные для поиска
      searchQuery: '',
      // Данные для пагинации
      currentPage: 1,
      // Данные для сортировки
      sortField: 'name'
    }
  },
  computed: {
    // Логика поиска
    filteredItems() { /* ... */ },
    // Логика пагинации
    paginatedItems() { /* ... */ },
    // Логика сортировки
    sortedItems() { /* ... */ }
  },
  methods: {
    // Метод поиска
    search() { /* ... */ },
    // Метод пагинации
    goToPage() { /* ... */ },
    // Метод сортировки
    sortBy() { /* ... */ }
  }
}
</script>

В Composition API логика сгруппирована:

vue
<script setup>
import { ref, computed } from 'vue'
 
// === Поиск ===
const searchQuery = ref('')
const filteredItems = computed(() => { /* ... */ })
function search() { /* ... */ }
 
// === Пагинация ===
const currentPage = ref(1)
const paginatedItems = computed(() => { /* ... */ })
function goToPage(page) { /* ... */ }
 
// === Сортировка ===
const sortField = ref('name')
const sortedItems = computed(() => { /* ... */ })
function sortBy(field) { /* ... */ }
</script>

Composables — переиспользование логики

Composable — это функция, которая использует Composition API для инкапсуляции и переиспользования логики:

Code Example 4: Что такое composable? Что делает функция useCounter и как она используется в компоненте?

js
// composables/useCounter.js
import { ref, computed } from 'vue'
 
export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const doubled = computed(() => count.value * 2)
 
  function increment() {
    count.value++
  }
 
  function decrement() {
    count.value--
  }
 
  return {
    count,
    doubled,
    increment,
    decrement
  }
}

Использование в компоненте:

vue
<template>
  <p>Счётчик: {{ count }}</p>
  <p>Удвоенный: {{ doubled }}</p>
  <button @click="increment">+</button>
  <button @click="decrement">-</button>
</template>
 
<script setup>
import { useCounter } from '@/composables/useCounter'
 
const { count, doubled, increment, decrement } = useCounter(10)
</script>

Пограничные кейсы

⚠️

В script setup нельзя использовать this — его просто нет. Все данные и методы доступны напрямую по имени.

Code Example 5: Как в этом коде работает обновление счётчика? Какие два варианта обращения к count показаны и какой из них правильный?

vue
<script setup>
import { ref } from 'vue'
 
const count = ref(0)
 
function increment() {
  // ❌ Нет this
  // this.count++
 
  // ✅ Прямой доступ через .value
  count.value++
}
</script>

Всё что объявлено в script setup автоматически доступно в шаблоне. Не нужен return.

Code Example 6: Что из объявленного в <script setup> доступно в <template>? Нужно ли явно возвращать что-то из setup?

vue
<script setup>
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
 
const message = ref('Привет')
 
function greet() {
  alert(message.value)
}
 
// Всё автоматически доступно в template:
// - message
// - greet
// - MyComponent
</script>
 
<template>
  <MyComponent />
  <p>{{ message }}</p>
  <button @click="greet">Приветствие</button>
</template>

Плюсы и минусы

АспектOptions APIComposition API
Порог входаНизкийВыше
Организация кодаПо типам опцийПо логическим функциям
ПереиспользованиеMixins (проблемно)Composables (удобно)
TypeScriptТребует дополнительной настройкиРаботает из коробки
Большие компонентыСложно читатьПроще организовать
Маленькие компонентыКомпактноЧуть больше кода

Когда использовать

Options API:

  • Простые компоненты
  • Команда с опытом Vue 2
  • Быстрое прототипирование

Composition API:

  • Сложные компоненты с большим количеством логики
  • Необходимость переиспользования логики
  • TypeScript проекты
  • Новые проекты на Vue 3

Вопросы интервьюера

Q: Зачем нужен Composition API?

Для лучшей организации кода, переиспользования логики через composables и улучшенной поддержки TypeScript.

Q: Можно ли использовать Options API и Composition API вместе?

Да, в одном проекте можно использовать оба подхода. Даже в одном компоненте можно комбинировать setup с options.

Q: Что такое composable?

Функция, использующая Composition API для инкапсуляции реактивной логики. Аналог mixins, но без их недостатков.

Q: Чем <script setup> отличается от обычного setup()?

<script setup> — синтаксический сахар. Компилируется в setup(), но с меньшим количеством boilerplate. Не нужен return.


Источники

Code Example 1: Options API vs Composition API — базовый счётчик

❓ Чем отличаются эти два варианта реализации счётчика? Какой подход использует Options API, а какой — Composition API?

Options API:

vue
<template>
  <p>{{ count }}</p>
  <button @click="increment">+1</button>
</template>
 
<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

Composition API:

vue
<template>
  <p>{{ count }}</p>
  <button @click="increment">+1</button>
</template>
 
<script setup>
import { ref } from 'vue'
 
const count = ref(0)
 
function increment() {
  count.value++
}
</script>

Code Example 2: Компонент с поиском и фильтрацией

❓ Что делает этот компонент? Какие возможности Composition API здесь используются для работы с данными и вычислениями?

vue
<template>
  <div>
    <input v-model="searchQuery" placeholder="Поиск..." />
    <ul>
      <li v-for="item in filteredItems" :key="item.id">
        {{ item.name }}
      </li>
    </ul>
    <p>Найдено: {{ itemCount }}</p>
  </div>
</template>
 
<script setup>
import { ref, computed } from 'vue'
 
const searchQuery = ref('')
const items = ref([
  { id: 1, name: 'Vue.js' },
  { id: 2, name: 'React' },
  { id: 3, name: 'Angular' }
])
 
const filteredItems = computed(() => {
  const query = searchQuery.value.toLowerCase()
  return items.value.filter(item =>
    item.name.toLowerCase().includes(query)
  )
})
 
const itemCount = computed(() => filteredItems.value.length)
</script>

Code Example 3: Организация кода по фичам

❓ Как организован код в Options API и в Composition API? Какой подход лучше масштабируется при росте компонента и почему?

Options API:

vue
<script>
export default {
  data() {
    return {
      searchQuery: '',
      currentPage: 1,
      sortField: 'name'
    }
  },
  computed: {
    filteredItems() { /* ... */ },
    paginatedItems() { /* ... */ },
    sortedItems() { /* ... */ }
  },
  methods: {
    search() { /* ... */ },
    goToPage() { /* ... */ },
    sortBy() { /* ... */ }
  }
}
</script>

Composition API:

vue
<script setup>
import { ref, computed } from 'vue'
 
// === Поиск ===
const searchQuery = ref('')
const filteredItems = computed(() => { /* ... */ })
function search() { /* ... */ }
 
// === Пагинация ===
const currentPage = ref(1)
const paginatedItems = computed(() => { /* ... */ })
function goToPage(page) { /* ... */ }
 
// === Сортировка ===
const sortField = ref('name')
const sortedItems = computed(() => { /* ... */ })
function sortBy(field) { /* ... */ }
</script>

Code Example 4: Composable-функция и её использование

❓ Что такое composable? Что делает функция useCounter и как она используется в компоненте?

js
// composables/useCounter.js
import { ref, computed } from 'vue'
 
export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const doubled = computed(() => count.value * 2)
 
  function increment() {
    count.value++
  }
 
  function decrement() {
    count.value--
  }
 
  return {
    count,
    doubled,
    increment,
    decrement
  }
}

Использование в компоненте:

vue
<template>
  <p>Счётчик: {{ count }}</p>
  <p>Удвоенный: {{ doubled }}</p>
  <button @click="increment">+</button>
  <button @click="decrement">-</button>
</template>
 
<script setup>
import { useCounter } from '@/composables/useCounter'
 
const { count, doubled, increment, decrement } = useCounter(10)
</script>

Code Example 5: Доступ к данным в script setup

❓ Как в этом коде работает обновление счётчика? Какие два варианта обращения к count показаны (Version A и Version B) и какой из них правильный?

Version A:

vue
<script setup>
import { ref } from 'vue'
 
const count = ref(0)
 
function increment() {
  this.count++
}
</script>

Version B:

vue
<script setup>
import { ref } from 'vue'
 
const count = ref(0)
 
function increment() {
  count.value++
}
</script>

Code Example 6: Автодоступность в шаблоне

❓ Что из объявленного в <script setup> доступно в <template>? Нужно ли явно возвращать что-то из setup?

vue
<script setup>
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
 
const message = ref('Привет')
 
function greet() {
  alert(message.value)
}
</script>
 
<template>
  <MyComponent />
  <p>{{ message }}</p>
  <button @click="greet">Приветствие</button>
</template>