docs: добавить стайлгайд nextjs-style-guide в репозиторий
- Добавлена документация SLM-архитектуры, базовых правил и прикладных разделов - Добавлены разделы: стили, SVG-спрайты, шаблоны генерации, PostCSS, REST, Realtime - Удалены устаревшие файлы (спрайты, скрипты, стили из app/)
This commit is contained in:
206
ai/nextjs-style-guide/data/rest/clients/hooks.md
Normal file
206
ai/nextjs-style-guide/data/rest/clients/hooks.md
Normal file
@@ -0,0 +1,206 @@
|
||||
---
|
||||
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).
|
||||
Reference in New Issue
Block a user