Стандарты
Composables
Composables — переиспользуемые функции с реактивным состоянием.
Composables — переиспользуемые функции с реактивным состоянием.
Когда создавать composable
| Ситуация | Создать composable |
|---|---|
| Логика используется в 2+ компонентах | ✅ |
| API запросы для сущности | ✅ |
| Сложная логика в компоненте (>50 строк) | ✅ |
| Работа с внешним сервисом (Ably, etc.) | ✅ |
| Простой утилитарный код | ❌ (→ utils/) |
Структура composables/
app/composables/
├── useApi.ts # HTTP клиент
├── useSession.ts # Сессия пользователя
├── useTableFilters.ts # Пагинация и фильтры
│
├── useOrders.ts # API заказов
├── useUsers.ts # API пользователей
├── useServices.ts # API услуг
├── useCompanies.ts # API компаний
│
├── useAbly.ts # Realtime
└── useMobile.ts # Responsive
Паттерн composable
Базовая структура
// app/composables/useOrders.ts
export function useOrders() {
// Зависимости
const api = useApi()
// Реактивное состояние (если нужно)
const orders = ref<Order[]>([])
const loading = ref(false)
const error = ref<Error | null>(null)
// Computed
const hasOrders = computed(() => orders.value.length > 0)
// Методы
async function fetchOrders(params?: OrderParams) {
loading.value = true
error.value = null
try {
const response = await api.get<PaginatedResponse<Order>>(
`/orders?${toQueryParams(params)}`
)
orders.value = response.data
return response
} catch (e) {
error.value = e as Error
throw e
} finally {
loading.value = false
}
}
async function createOrder(data: CreateOrderDto) {
return api.post<Order>('/orders', data)
}
async function updateOrder(id: string, data: UpdateOrderDto) {
return api.patch<Order>(`/orders/${id}`, data)
}
async function deleteOrder(id: string) {
return api.delete(`/orders/${id}`)
}
// Return
return {
// State
orders,
loading,
error,
// Computed
hasOrders,
// Methods
fetchOrders,
createOrder,
updateOrder,
deleteOrder,
}
}
Типизация
TypeScript автоматически выводит типы возврата из composable. Не нужно создавать отдельный интерфейс:
// ❌ Избыточно — интерфейс дублирует то, что TS выведет сам
interface UseOrdersReturn {
orders: Ref<Order[]>
loading: Ref<boolean>
error: Ref<Error | null>
// ...
}
export function useOrders(): UseOrdersReturn { ... }
// ✅ Хорошо — тип возврата выводится автоматически
export function useOrders() {
const orders = ref<Order[]>([]) // Тип нужен — пустой массив
const loading = ref(false) // Тип выводится
const error = ref<Error | null>(null) // Тип нужен — nullable
return { orders, loading, error }
// TypeScript выведет: { orders: Ref<Order[]>, loading: Ref<boolean>, ... }
}
См. standard-typescript — правило 6.
Использование в компонентах
<script setup lang="ts">
// Composable автоматически импортируется
const { orders, loading, fetchOrders, createOrder } = useOrders()
// Фетч при монтировании
onMounted(() => {
fetchOrders({ page: 1, status: 'active' })
})
// Создание заказа
async function handleSubmit(data: CreateOrderDto) {
await createOrder(data)
await fetchOrders() // Рефетч
}
</script>
<template>
<div v-if="loading">Загрузка...</div>
<OrderTable v-else :data="orders" />
</template>
Правила
1. Имя начинается с use
// ✅ Хорошо
useOrders()
useApi()
useTableFilters()
// ❌ Плохо
orders()
apiClient()
tableFilters()
2. Возвращай объект, не массив
// ✅ Хорошо — объект с именованными свойствами
return { orders, loading, fetchOrders }
// ❌ Плохо — массив (сложно запомнить порядок)
return [orders, loading, fetchOrders]
3. Не храни состояние между вызовами (если не нужен singleton)
// Каждый вызов создаёт новое состояние
export function useOrders() {
const orders = ref([]) // Новый ref при каждом вызове
return { orders }
}
// Singleton — состояние общее
const orders = ref([]) // Вне функции
export function useOrdersSingleton() {
return { orders }
}
4. Выноси бизнес-логику из компонентов
<!-- ❌ Плохо — логика в компоненте -->
<script setup>
const api = useApi()
const orders = ref([])
async function fetchOrders() {
orders.value = await api.get('/orders')
}
async function createOrder(data) {
await api.post('/orders', data)
await fetchOrders()
}
</script>
<!-- ✅ Хорошо — логика в composable -->
<script setup>
const { orders, fetchOrders, createOrder } = useOrders()
onMounted(fetchOrders)
</script>
Чек-лист
- Имя начинается с
use - Возвращает объект
- Типизированы параметры и API ответы (возврат выводится автоматически)
- Не содержит UI логику
- Документирован JSDoc (если сложный)