--- 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(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 ( {children} ) } ``` Клиентский компонент при этом ничего не знает про 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(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).