Разделение логики: компоненты, composables, сервисы, сторы

↑ 8.2 Архитектура фронтенда и паттерны · ← Предыдущая · → Следующая

Желательно~3 мин чтенияУровень: senior

Зачем это на собесе

Главный практический вопрос: где что должно лежать во Vue-приложении.

Слои

СлойОтветственностьЗнает о
Компонентотображение, обработка пользовательского вводаprops, composables, store
Composableпереиспользуемая реактивная логика (UI и данные)сервисы, другие composables
Store (Pinia)общее состояние приложения, кэш данныхсервисы
Service / APIобмен с бэкендом, преобразование DTOHTTP-клиент
Utilsчистые функцииничего

Как решать, где хранить

  • локально нужное одному компоненту → ref в компоненте;
  • переиспользуемая логика с реактивностью → composable;
  • разделяется между несколькими страницами или переживает навигацию → Pinia;
  • данные с сервера с кэшем, повторной загрузкой, инвалидацией → TanStack Query (Vue Query);
  • состояние в URL (фильтры, страница) → query-параметры роутера;
  • форма → локально (vee-validate, FormKit).

Пример

// services/orders.ts
export const ordersApi = {
  list: (params: OrdersQuery) => http.get<OrderDto[]>('/orders', { params }).then(r => r.data.map(toOrder)),
}
 
// composables/useOrders.ts
export function useOrders(query: Ref<OrdersQuery>) {
  return useQuery({ queryKey: ['orders', query], queryFn: () => ordersApi.list(query.value) })
}
<script setup lang="ts">
const filters = ref<OrdersQuery>({ page: 1 })
const { data: orders, isLoading } = useOrders(filters)
</script>
<template><OrdersTable :rows="orders" :loading="isLoading" /></template>

Антипаттерны

  • HTTP-запросы прямо в компонентах;
  • вся логика в store, включая локальную;
  • store как кэш серверных данных вручную (без инвалидации);
  • «умные» компоненты, влезающие в глобальное состояние без нужды;
  • события emit через 5 уровней вместо provide/inject или стора.

Тестируемость

Логика в composables и сервисах тестируется без DOM, компоненты — через Testing Library.

Вопросы с ответами