PurpleSchool — курсы программирования онлайн
  • Бесплатно
    • Курсы
    • JavaScript Основы разработкиPython Основы PythonCSS CSS FlexboxКарта развития
    • База знанийИконка стрелки
    • Новостные рассылкиИконка стрелки
  • Карьерные пути
    • Frontend React разработчик
    • Frontend Vue разработчик
    • Backend разработчик Node.js
    • Fullstack разработчик React / Node.js
    • Mobile разработчик React Native
    • Backend разработчик Golang
    • Devops инженер
    • Backend разработчик Python
  • О нас
    • Отзывы
    • Реферальная программа
    • О компании
    • Контакты
  • Иконка открытия меню
    • Сообщество
    • PurpleПлюс
    • AI тренажёр
    • Проекты
PurpleSchool — платформа бесплатных roadmap и курсов для разработчиков
ютуб иконка
Telegram иконка
VK иконка
VK иконка
Курсы
ГлавнаяКаталог курсовFrontendBackendFullstack
Практика
КарьераПроектыPurpleПлюс
Материалы
БлогБаза знаний
Документы
Договор офертаПолитика конфиденциальностиПроверка сертификатаМиграция курсовРеферальная программа
Реквизиты
ИП Ларичев Антон АндреевичИНН 773373765379contact@purpleschool.ru

PurpleSchool © 2020 -2026 Все права защищены

  • Курсы
    • FrontendИконка стрелки
    • AI разработкаИконка стрелки
    • BackendИконка стрелки
    • DevOpsИконка стрелки
    • MobileИконка стрелки
    • ТестированиеИконка стрелки
    • Soft-skillsИконка стрелки
    • ДизайнИконка стрелки
    Иконка слояПерейти в каталог курсов
  • PurpleSchool — курсы программирования онлайн
    • Сообщество
    • PurpleПлюс
    • AI тренажёр
    • Проекты
    Главная
    Сообщество
    Vue 3 Composition API: 5 реальных паттернов, которые упрощают код

    Vue 3 Composition API: 5 реальных паттернов, которые упрощают код

    Аватар автора Vue 3 Composition API: 5 реальных паттернов, которые упрощают код

    Антон Ларичев

    Иконка календаря23 марта 2026
    vuetypescriptjavascriptmiddleИконка уровня middle
    Картинка поста Vue 3 Composition API: 5 реальных паттернов, которые упрощают код

    Введение

    Vue 3 Composition API кардинально изменил подход к организации логики в компонентах. Вместо разбросанных по Options API свойств data, methods и computed, теперь можно группировать связанную логику в composable-функции и переиспользовать её между компонентами.

    В этой статье разберём 5 реальных паттернов Vue 3 Composition API, которые применяются в продакшен-проектах. Каждый паттерн содержит готовый пример на TypeScript, который можно адаптировать под свои задачи. Если вы уже используете <script setup> и хотите писать чище и эффективнее, эти composables будут полезны.

    Паттерн 1: useAsync для загрузки данных

    Самый частый сценарий в любом приложении — запрос данных с сервера. Вместо копирования логики loading, error, data в каждый компонент, выносим всё в composable.

    import { ref, type Ref } from 'vue'
    
    interface UseAsyncReturn<T> {
      data: Ref<T | null>
      error: Ref<string | null>
      loading: Ref<boolean>
      execute: () => Promise<void>
    }
    
    export function useAsync<T>(asyncFn: () => Promise<T>): UseAsyncReturn<T> {
      const data = ref<T | null>(null) as Ref<T | null>
      const error = ref<string | null>(null)
      const loading = ref(false)
    
      const execute = async () => {
        loading.value = true
        error.value = null
        try {
          data.value = await asyncFn()
        } catch (e) {
          error.value = e instanceof Error ? e.message : 'Неизвестная ошибка'
        } finally {
          loading.value = false
        }
      }
    
      return { data, error, loading, execute }
    }
    

    Как использовать useAsync в компоненте

    <script setup lang="ts">
    import { useAsync } from '@/composables/useAsync'
    import { fetchUsers } from '@/api/users'
    
    // Вызываем composable и сразу запускаем загрузку
    const { data: users, loading, error, execute } = useAsync(fetchUsers)
    execute()
    </script>
    
    <template>
      <div v-if="loading">Загрузка...</div>
      <div v-else-if="error">{{ error }}</div>
      <ul v-else>
        <li v-for="user in users" :key="user.id">{{ user.name }}</li>
      </ul>
    </template>
    

    Теперь логика загрузки данных инкапсулирована: в компоненте остаётся только вызов и шаблон.

    Паттерн 2: useFormField с валидацией через computed

    Формы — второй по частоте сценарий, где код быстро превращается в кашу. Composable useFormField связывает значение поля с правилами валидации через computed.

    import { ref, computed } from 'vue'
    
    type ValidationRule = (value: string) => string | true
    
    export function useFormField(initialValue: string, rules: ValidationRule[] = []) {
      const value = ref(initialValue)
      const touched = ref(false)
    
      // Реактивно вычисляем ошибку при каждом изменении value
      const error = computed(() => {
        if (!touched.value) return null
        for (const rule of rules) {
          const result = rule(value.value)
          if (result !== true) return result
        }
        return null
      })
    
      const isValid = computed(() => error.value === null && touched.value)
    
      const blur = () => { touched.value = true }
      const reset = () => {
        value.value = initialValue
        touched.value = false
      }
    
      return { value, error, isValid, touched, blur, reset }
    }
    

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

    <script setup lang="ts">
    import { useFormField } from '@/composables/useFormField'
    
    const email = useFormField('', [
      (v) => v.length > 0 || 'Обязательное поле',
      (v) => v.includes('@') || 'Некорректный email',
    ])
    </script>
    
    <template>
      <input v-model="email.value" @blur="email.blur" />
      <span v-if="email.error">{{ email.error }}</span>
    </template>
    

    Каждое поле формы становится самостоятельным реактивным объектом. Валидация срабатывает автоматически при изменении значения.

    Паттерн 3: readonly-состояние через provide/inject

    Когда нескольким компонентам нужен доступ к общему состоянию, часто возникает соблазн использовать глобальный store. Но для локальных задач хватает связки provide, inject и readonly.

    import { ref, provide, inject, readonly, type InjectionKey, type Ref } from 'vue'
    
    interface NotificationState {
      message: Ref<string>
      show: (msg: string) => void
      hide: () => void
    }
    
    const NotificationKey: InjectionKey<NotificationState> = Symbol('notification')
    
    // Вызывается в корневом компоненте
    export function provideNotification() {
      const message = ref('')
    
      const show = (msg: string) => { message.value = msg }
      const hide = () => { message.value = '' }
    
      // Потребители получают readonly-версию состояния
      provide(NotificationKey, {
        message: readonly(message) as Ref<string>,
        show,
        hide,
      })
    }
    
    // Вызывается в дочерних компонентах
    export function useNotification(): NotificationState {
      const state = inject(NotificationKey)
      if (!state) throw new Error('provideNotification не вызван')
      return state
    }
    

    Этот паттерн Vue 3 Composition API позволяет избежать props drilling и при этом сохранить контроль над мутациями: дочерние компоненты могут вызывать show и hide, но не могут напрямую менять message.

    Паттерн 4: useDebounce для отложенных вычислений

    Поиск с автодополнением, фильтрация списков, автосохранение — все эти задачи требуют debounce. Composable с watch решает это элегантно.

    import { ref, watch, type Ref } from 'vue'
    
    export function useDebounce<T>(source: Ref<T>, delay = 300): Ref<T> {
      const debounced = ref(source.value) as Ref<T>
      let timeout: ReturnType<typeof setTimeout>
    
      watch(source, (newValue) => {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          debounced.value = newValue
        }, delay)
      })
    
      return debounced
    }
    

    Пример: поиск с debounce во Vue 3

    <script setup lang="ts">
    import { ref, watch } from 'vue'
    import { useDebounce } from '@/composables/useDebounce'
    import { useAsync } from '@/composables/useAsync'
    import { searchProducts } from '@/api/products'
    
    const query = ref('')
    const debouncedQuery = useDebounce(query, 400)
    
    const { data: results, execute } = useAsync(() => searchProducts(debouncedQuery.value))
    
    // Запускаем поиск при изменении отложенного значения
    watch(debouncedQuery, () => {
      if (debouncedQuery.value.length >= 2) execute()
    })
    </script>
    
    <template>
      <input v-model="query" placeholder="Поиск товаров..." />
      <ul v-if="results">
        <li v-for="item in results" :key="item.id">{{ item.name }}</li>
      </ul>
    </template>
    

    Обратите внимание, как composables комбинируются: useDebounce и useAsync работают вместе, при этом каждый отвечает за свою задачу.

    Паттерн 5: composable-синглтон для глобального состояния

    Иногда нужно глобальное реактивное состояние без тяжёлых стейт-менеджеров. Если вынести ref за пределы функции, все вызовы composable будут ссылаться на один и тот же экземпляр.

    import { ref, computed } from 'vue'
    
    // Состояние создаётся один раз при импорте модуля
    const theme = ref<'light' | 'dark'>('light')
    
    export function useTheme() {
      const isDark = computed(() => theme.value === 'dark')
    
      const toggle = () => {
        theme.value = theme.value === 'light' ? 'dark' : 'light'
        document.documentElement.setAttribute('data-theme', theme.value)
      }
    
      const set = (value: 'light' | 'dark') => {
        theme.value = value
        document.documentElement.setAttribute('data-theme', value)
      }
    
      return { theme, isDark, toggle, set }
    }
    

    В любом компоненте вызов useTheme() вернёт ссылку на один и тот же реактивный theme. Это паттерн синглтона на уровне модуля, который работает без дополнительных зависимостей.

    Частые ошибки при работе с Composition API

    При создании composables разработчики допускают типичные ошибки:

    Потеря реактивности при деструктуризации. Если composable возвращает reactive-объект, деструктуризация потеряет реактивность. Возвращайте объект с ref-значениями или используйте toRefs.

    // Плохо: реактивность потеряна
    const { count } = useCounter() // count — обычное число
    
    // Хорошо: реактивность сохранена
    const { count } = useCounter() // count — Ref<number>
    

    Вызов composables вне setup. Composables, использующие onMounted, watch или provide, должны вызываться внутри <script setup> или функции setup(). Иначе Vue не сможет привязать хуки жизненного цикла к текущему экземпляру компонента.

    Слишком крупные composables. Composable на 200 строк — это тот же монолит, только в другой обёртке. Разбивайте на мелкие функции и комбинируйте их.

    Заключение

    Паттерны Vue 3 Composition API — это не абстрактная теория, а инструменты для ежедневной работы. Composables useAsync, useFormField, useDebounce, useTheme и provide/inject с readonly покрывают большинство типовых задач. Главное правило — каждый composable решает одну задачу, а сложное поведение собирается из простых кирпичиков. Используйте ref для примитивов, computed для производных значений, watch для побочных эффектов, и ваш код останется чистым и поддерживаемым.

    Иконка глаза11

    Комментарии

    0

    Постройте личный план изучения Основы Git до уровня Middle — бесплатно!

    Основы Git — часть карты развития Frontend, Backend, DevOps

    • step100+ шагов развития
    • lessons30 бесплатных лекций
    • lessons300 бонусных рублей на счет

    Бесплатные лекции

    Лучшие курсы по теме

    изображение курса

    Основы JavaScript

    Антон Ларичев
    AI-тренажеры
    Практика в студии
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.8
    3 999 ₽ 6 990 ₽
    Подробнее
    изображение курса

    Продвинутый JavaScript

    Антон Ларичев
    AI-тренажеры
    Практика в студии
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.9
    3 999 ₽ 6 990 ₽
    Подробнее
    изображение курса

    TypeScript с нуля

    Антон Ларичев
    AI-тренажеры
    Практика в студии
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.8
    3 999 ₽ 6 990 ₽
    Подробнее
    Иконка чипа0