style: Обновление код стайла

This commit is contained in:
2026-05-08 19:34:39 +03:00
parent fec5ca78d0
commit 867ea244cf
24 changed files with 661 additions and 546 deletions

View File

@@ -0,0 +1,313 @@
---
title: GET-хуки REST-клиента
description: Прозрачные SWR-обёртки над GET-методами REST-клиента.
keywords: [rest, swr, get-хуки, client components, infra]
---
# GET-хуки REST-клиента
Прозрачные SWR-обёртки над GET-методами REST-клиента.
## Зачем нужны
GET-хуки нужны, чтобы Client Components получали REST-данные через SWR, но не работали с `useSWR`, ключами кеша и fetcher напрямую.
## Где лежат
GET-хуки принадлежат REST-клиенту конкретного сервиса и живут рядом с ним:
```text
src/infra/
└── pet-store-api/
├── client.ts
├── generated/
├── hooks/
│ ├── use-get-pet-list.hook.ts
│ ├── use-get-pet-detail.hook.ts
│ └── index.ts
├── types/
└── index.ts
```
## Контракт
- Один GET-хук = один GET-метод клиента.
- Имя GET-хука начинается с `useGet`: `useGetPetList`, `useGetPetDetail`.
- Имя файла начинается с `use-get`: `use-get-pet-list.hook.ts`.
- Хук принимает `params?: GeneratedParams | null` и `config?: SWRConfiguration<Data>`.
- Для GET-метода без параметров хук принимает только `config?: SWRConfiguration<Data>`.
- Key-функция принимает те же `params`, что и хук.
- Key-функция возвращает `null`, если обязательные параметры не готовы.
- Проверка готовности запроса живёт в key-функции, а не в теле хука.
- Хук вызывает `useSWR` один раз и безусловно.
- Fetcher не проверяет `null`, не бросает ошибку и не вызывает метод клиента с `null`.
- Внутри только SWR-механика: key, fetcher, `useSWR`, `config`.
- Хук возвращает тип ответа API: generated-тип или DTO из `types/`.
- Хук не объединяет несколько запросов.
- Хук не маппит DTO в доменную модель.
- Хук не вычисляет бизнес-флаги: `isAuth`, `canEdit`, `hasAccess`, `hasPets`.
- Хук не вызывает тосты, модалки, редиректы и не пишет UI-состояние.
## Формат SWR-ключа
SWR-ключ GET-хука всегда создаётся отдельной экспортируемой функцией.
Формат ключа:
```ts
['pet-store-api', '/pet/10'] as const
```
- Первый элемент — имя API-сервиса или REST-клиента в `kebab-case`.
- Второй элемент — endpoint запроса: path и query string.
- Key-функция возвращает `null`, когда запрос нельзя выполнять.
- Key-функция нужна и GET-хуку, и `SWRConfig fallback`.
- Не используйте произвольные части вроде `['pet-store-api', 'pet', 'detail', params]`.
- Не используйте только строку endpoint без имени сервиса.
Примеры ключей:
```ts
export const getPetDetailKey = (params?: GetPetByIdParams | null) => {
if (!params?.petId) {
return null
}
return ['pet-store-api', `/pet/${params.petId}`] as const
}
```
```ts
export const getPetListKey = (params?: FindPetsByStatusParams | null) => {
if (!params?.status) {
return null
}
return ['pet-store-api', `/pet/findByStatus?status=${params.status}`] as const
}
```
```ts
export const getPetListByTagsKey = (params?: FindPetsByTagsParams | null) => {
if (!params?.tags.length) {
return null
}
return ['pet-store-api', `/pet/findByTags?tags=${params.tags.join(',')}`] as const
}
```
Если API допускает `0` как валидный идентификатор, не используйте проверку `!params?.id`. В таком случае проверяйте `null` и `undefined` явно.
## Пример списка
```ts
// src/infra/pet-store-api/hooks/use-get-pet-list.hook.ts
import type { SWRConfiguration } from 'swr'
import useSWR from 'swr'
import { petStoreApi } from '../client'
import type {
FindPetsByStatusParams,
Pet,
} from '../generated/pet-store-api.generated'
export const getPetListKey = (params?: FindPetsByStatusParams | null) => {
if (!params?.status) {
return null
}
return ['pet-store-api', `/pet/findByStatus?status=${params.status}`] as const
}
/**
* Получает список питомцев по статусу.
*/
export const useGetPetList = (
params?: FindPetsByStatusParams | null,
config?: SWRConfiguration<Pet[]>,
) => {
const key = getPetListKey(params)
const fetcher = () => petStoreApi.pet.findPetsByStatus(
params as FindPetsByStatusParams,
)
return useSWR<Pet[]>(key, fetcher, config)
}
```
`params as FindPetsByStatusParams` допустим только в fetcher: готовность параметров проверена в key-функции, а при `key = null` SWR не вызывает fetcher.
## Пример detail-запроса
```ts
// src/infra/pet-store-api/hooks/use-get-pet-detail.hook.ts
import type { SWRConfiguration } from 'swr'
import useSWR from 'swr'
import { petStoreApi } from '../client'
import type { GetPetByIdParams, Pet } from '../generated/pet-store-api.generated'
export const getPetDetailKey = (params?: GetPetByIdParams | null) => {
if (!params?.petId) {
return null
}
return ['pet-store-api', `/pet/${params.petId}`] as const
}
/**
* Получает детальную карточку питомца с кешированием результата.
*/
export const useGetPetDetail = (
params?: GetPetByIdParams | null,
config?: SWRConfiguration<Pet>,
) => {
const key = getPetDetailKey(params)
const fetcher = () => petStoreApi.pet.getPetById(params as GetPetByIdParams)
return useSWR<Pet>(key, fetcher, config)
}
```
## Пример без параметров
```ts
// src/infra/pet-store-api/hooks/use-get-store-inventory.hook.ts
import type { SWRConfiguration } from 'swr'
import useSWR from 'swr'
import { petStoreApi } from '../client'
import type { StoreInventory } from '../types'
export const getStoreInventoryKey = () => {
return ['pet-store-api', '/store/inventory'] as const
}
/**
* Получает инвентарь магазина.
*/
export const useGetStoreInventory = (
config?: SWRConfiguration<StoreInventory>,
) => {
return useSWR<StoreInventory>(
getStoreInventoryKey(),
() => petStoreApi.store.getInventory(),
config,
)
}
```
Если generated-метод возвращает безымянный тип вроде `Record<string, number>`, а тип нужен наружу, вынесите его в `types/`.
## Отложенный запрос
GET-хук может принимать `null` или `undefined` для обязательных параметров. Это означает, что параметры ещё не готовы и запрос выполнять нельзя.
```ts
const key = getPetDetailKey(params)
```
Если `params` не готов, key-функция вернёт `null`. SWR не вызовет fetcher для `null`-ключа.
Не добавляйте отдельные `isReady`, `throw new Error(...)` и условный вызов `useSWR`.
## Экспорт
```ts
// src/infra/pet-store-api/hooks/index.ts
export { getPetListKey, useGetPetList } from './use-get-pet-list.hook'
export { getPetDetailKey, useGetPetDetail } from './use-get-pet-detail.hook'
export {
getStoreInventoryKey,
useGetStoreInventory,
} from './use-get-store-inventory.hook'
```
```ts
// src/infra/pet-store-api/index.ts
export { petStoreApi } from './client'
export type {
FindPetsByStatusParams,
GetPetByIdParams,
Pet,
} from './generated/pet-store-api.generated'
export { PetStatusEnum, StatusEnum } from './generated/pet-store-api.generated'
export * from './hooks'
export type { StoreInventory } from './types'
```
Наружу импортируют только из `infra/pet-store-api`, не из `generated/` и не из `hooks/` напрямую.
## Где заканчивается infra
```ts
// Хорошо: infra, прозрачный GET-хук
const { data: pets } = useGetPetList({ status: StatusEnum.Available })
```
```ts
// Хорошо: business, доменная интерпретация
export const useAvailablePets = () => {
const query = useGetPetList({ status: StatusEnum.Available })
return {
...query,
hasPets: Boolean(query.data?.length),
}
}
```
`hasPets` — не часть GET-запроса, поэтому он не добавляется в `useGetPetList`.
## Что запрещено
```ts
// Плохо — useSWR в компоненте
const { data } = useSWR(
['pet-store-api', '/pet/findByStatus?status=available'],
() => petStoreApi.pet.findPetsByStatus({ status: StatusEnum.Available }),
)
// Плохо — проверка готовности размазана по хуку
export const useGetPetDetail = (params?: GetPetByIdParams | null) => {
const key = params?.petId ? getPetDetailKey(params) : null
const fetcher = () => {
if (!params?.petId) {
throw new Error('Pet id is required')
}
return petStoreApi.pet.getPetById(params)
}
return useSWR<Pet>(key, fetcher)
}
// Плохо — условный вызов useSWR нарушает rules of hooks
export const useGetPetDetail = (params?: GetPetByIdParams | null) => {
const key = getPetDetailKey(params)
if (key === null) {
return useSWR(null, null)
}
return useSWR(key, () => petStoreApi.pet.getPetById(params))
}
// Плохо — несколько GET внутри infra-хука
export const usePetDashboard = () => {
const available = useGetPetList({ status: StatusEnum.Available })
const sold = useGetPetList({ status: StatusEnum.Sold })
return { available, sold }
}
// Плохо — бизнес-флаг внутри GET-хука REST-клиента
export const useGetPetList = (params?: FindPetsByStatusParams | null) => {
const query = useSWR(...)
return {
...query,
hasPets: Boolean(query.data?.length),
}
}
```
Подробное потребление таких хуков описано в стратегии [Клиентский GET-хук](../../data-fetch/client-get-hook.md).