- Добавлена документация SLM-архитектуры, базовых правил и прикладных разделов - Добавлены разделы: стили, SVG-спрайты, шаблоны генерации, PostCSS, REST, Realtime - Удалены устаревшие файлы (спрайты, скрипты, стили из app/)
207 lines
7.4 KiB
Markdown
207 lines
7.4 KiB
Markdown
---
|
||
title: GET-хуки REST-клиента
|
||
description: Прозрачные SWR-обёртки над GET-методами REST-клиента.
|
||
keywords: [rest, swr, get-хуки, client components, infrastructure]
|
||
---
|
||
|
||
# GET-хуки REST-клиента
|
||
|
||
GET-хуки REST-клиента — прозрачные SWR-обёртки над GET-методами API-клиента. Они нужны, чтобы Client Components получали данные с кешированием, дедупликацией и ревалидацией, не работая с `useSWR` напрямую.
|
||
|
||
## Где лежат
|
||
|
||
GET-хуки принадлежат REST-клиенту конкретного сервиса и живут рядом с ним:
|
||
|
||
```text
|
||
src/infrastructure/
|
||
└── pet-store-api/
|
||
├── client.ts
|
||
├── generated/
|
||
├── hooks/
|
||
│ ├── use-get-pet-list.hook.ts
|
||
│ ├── use-get-pet-detail.hook.ts
|
||
│ └── index.ts
|
||
└── index.ts
|
||
```
|
||
|
||
## Контракт
|
||
|
||
- Один GET-хук = один GET-метод клиента.
|
||
- Имя GET-хука начинается с `useGet`: `useGetPetList`, `useGetPetDetail`.
|
||
- Имя файла начинается с `use-get`: `use-get-pet-list.hook.ts`.
|
||
- Хук принимает только параметры GET-метода и `config?: SWRConfiguration`.
|
||
- Что передали хуку, то он передаёт в GET-метод.
|
||
- Внутри только SWR-механика: key, fetcher, `useSWR`, `config`.
|
||
- Хук возвращает тип ответа API: generated-тип или DTO из `types/`.
|
||
- Хук не объединяет несколько запросов.
|
||
- Хук не маппит DTO в доменную модель.
|
||
- Хук не вычисляет бизнес-флаги: `isAuth`, `canEdit`, `hasAccess`, `hasPets`.
|
||
- Хук не вызывает тосты, модалки, редиректы и не пишет UI-состояние.
|
||
|
||
## Пример списка
|
||
|
||
```ts
|
||
// src/infrastructure/pet-store-api/hooks/use-get-pet-list.hook.ts
|
||
import useSWR from 'swr'
|
||
import type { SWRConfiguration } from 'swr'
|
||
import { petStoreApi } from '../client'
|
||
import type { Pet } from '../generated/pet-store-api.generated'
|
||
|
||
export type PetStatus = 'available' | 'pending' | 'sold'
|
||
|
||
export const getPetListKey = (status: PetStatus) =>
|
||
['pet-store-api', 'pet', 'list', status] as const
|
||
|
||
/**
|
||
* Получение списка питомцев по статусу.
|
||
*/
|
||
export const useGetPetList = (status: PetStatus | null, config?: SWRConfiguration) => {
|
||
const isReady = status !== null
|
||
const key = isReady ? getPetListKey(status) : null
|
||
const fetcher = () => petStoreApi.pet.findPetsByStatus({ status })
|
||
|
||
return useSWR<Pet[]>(key, fetcher, config)
|
||
}
|
||
```
|
||
|
||
Функция `getPetListKey` нужна, чтобы один и тот же SWR-ключ использовался внутри GET-хука и при передаче начальных данных через `SWRConfig fallback`.
|
||
|
||
Пример начальных данных для клиентского хука:
|
||
|
||
```tsx
|
||
import type { ReactNode } from 'react'
|
||
import { SWRConfig, unstable_serialize } from 'swr'
|
||
import {
|
||
getPetListKey,
|
||
petStoreApi,
|
||
} from 'infrastructure/pet-store-api'
|
||
|
||
export default function PetsLayout({ children }: { children: ReactNode }) {
|
||
const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
|
||
|
||
return (
|
||
<SWRConfig
|
||
value={{
|
||
fallback: {
|
||
[unstable_serialize(getPetListKey('available'))]: petsPromise,
|
||
},
|
||
}}
|
||
>
|
||
{children}
|
||
</SWRConfig>
|
||
)
|
||
}
|
||
```
|
||
|
||
Клиентский компонент при этом ничего не знает про preload/fallback и продолжает вызывать обычный хук:
|
||
|
||
```tsx
|
||
const { data: pets } = useGetPetList('available')
|
||
```
|
||
|
||
## Пример detail-запроса
|
||
|
||
```ts
|
||
// src/infrastructure/pet-store-api/hooks/use-get-pet-detail.hook.ts
|
||
import useSWR from 'swr'
|
||
import type { SWRConfiguration } from 'swr'
|
||
import { petStoreApi } from '../client'
|
||
import type { Pet } from '../generated/pet-store-api.generated'
|
||
|
||
export const getPetDetailKey = (id: number) =>
|
||
['pet-store-api', 'pet', 'detail', id] as const
|
||
|
||
/**
|
||
* Получение питомца по идентификатору.
|
||
*/
|
||
export const useGetPetDetail = (id: number | null, config?: SWRConfiguration) => {
|
||
const isReady = id !== null
|
||
const key = isReady ? getPetDetailKey(id) : null
|
||
const fetcher = () => petStoreApi.pet.getPetById(id)
|
||
|
||
return useSWR<Pet>(key, fetcher, config)
|
||
}
|
||
```
|
||
|
||
## Отложенный запрос через `null`
|
||
|
||
GET-хук может принимать `null` для обязательного параметра. `null` означает, что параметр ещё не готов и запрос выполнять нельзя.
|
||
|
||
Внутри хука это выражается через `isReady`: если параметр не готов, ключ SWR становится `null`, и SWR не вызывает fetcher.
|
||
|
||
```ts
|
||
const isReady = id !== null
|
||
const key = isReady ? getPetDetailKey(id) : null
|
||
```
|
||
|
||
`null` не передаётся в метод клиента. Key-функция принимает только готовые параметры, поэтому её можно безопасно использовать для начальных данных через `SWRConfig fallback`.
|
||
|
||
Для числовых идентификаторов не используйте проверку `if (id)`: значение `0` тоже валидное число. Проверяйте явно: `id !== null`.
|
||
|
||
## Экспорт
|
||
|
||
```ts
|
||
// src/infrastructure/pet-store-api/hooks/index.ts
|
||
export { getPetListKey, useGetPetList } from './use-get-pet-list.hook'
|
||
export type { PetStatus } from './use-get-pet-list.hook'
|
||
export { getPetDetailKey, useGetPetDetail } from './use-get-pet-detail.hook'
|
||
```
|
||
|
||
```ts
|
||
// src/infrastructure/pet-store-api/index.ts
|
||
export { petStoreApi } from './client'
|
||
export type { Pet } from './generated/pet-store-api.generated'
|
||
export * from './hooks'
|
||
```
|
||
|
||
## Где заканчивается infrastructure
|
||
|
||
```ts
|
||
// Хорошо: infrastructure, прозрачный GET-хук
|
||
const { data: pets } = useGetPetList('available')
|
||
```
|
||
|
||
```ts
|
||
// Хорошо: business, доменная интерпретация
|
||
export const useAvailablePets = () => {
|
||
const query = useGetPetList('available')
|
||
|
||
return {
|
||
...query,
|
||
hasPets: Boolean(query.data?.length),
|
||
}
|
||
}
|
||
```
|
||
|
||
`hasPets` — не часть GET-запроса, поэтому он не добавляется в `useGetPetList`.
|
||
|
||
## Что запрещено
|
||
|
||
```ts
|
||
// Плохо — useSWR в компоненте
|
||
const { data } = useSWR(
|
||
['pet-store-api', 'pet', 'list', status],
|
||
() => petStoreApi.pet.findPetsByStatus({ status }),
|
||
)
|
||
|
||
// Плохо — несколько GET внутри infrastructure-хука
|
||
export const usePetDashboard = () => {
|
||
const available = useGetPetList('available')
|
||
const sold = useGetPetList('sold')
|
||
|
||
return { available, sold }
|
||
}
|
||
|
||
// Плохо — бизнес-флаг внутри GET-хука REST-клиента
|
||
export const useGetPetList = (status: PetStatus) => {
|
||
const query = useSWR(...)
|
||
|
||
return {
|
||
...query,
|
||
hasPets: Boolean(query.data?.length),
|
||
}
|
||
}
|
||
```
|
||
|
||
Подробное потребление таких хуков описано в стратегии [Клиентский GET-хук](../strategies/client-get-hook.md).
|