Клиентские компоненты
В клиентских компонентах данные получаются через готовые хуки, которые экспортируются из модуля API. SWR инкапсулирован в хуке — компонент не знает про useSWR, ключи и fetcher.
Создание клиента и хуков — Автоматическая / Ручная генерация.
Правила
- Только готовые хуки. В компоненте —
usePostDetail(slug), неuseSWR(['post', slug], () => api.posts.get(slug)). useSWRпишется один раз — вhooks/модуля API. В клиентских компонентах никогда напрямую.- Прямой вызов методов клиента в
useEffectзапрещён. Это потеря кеша, повторные запросы и гонки. - Мутации — через
useSWRMutation, тоже инкапсулированный в хуке. В компоненте вызывается готовыйtrigger.
Чтение
'use client'
import { usePostDetail } from 'infrastructure/pet-project-api'
export function PostView({ slug }: { slug: string }) {
const { data: post, error, isLoading } = usePostDetail(slug)
if (isLoading) return <Spinner />
if (error) return <ErrorView error={error} />
return <article>{post?.title}</article>
}В компоненте нет useSWR, нет ключей, нет fetcher — только готовый хук.
Параметризованный запрос
Хук сам обрабатывает «нет параметра — нет запроса». В компоненте можно безопасно передавать null:
'use client'
import { useUserDetail } from 'infrastructure/pet-project-api'
export function UserProfile({ userId }: { userId: string | null }) {
const { data: user } = useUserDetail(userId)
if (!userId) return <EmptyState />
return <UserCard user={user} />
}Внутри useUserDetail ключ становится null, когда userId не задан, и SWR не делает запрос — это поведение зашито в хук, потребитель об этом не думает.
Мутации
Мутации тоже оборачиваются в хук модуля API:
// src/infrastructure/pet-project-api/hooks/use-create-user.hook.ts
import useSWRMutation from 'swr/mutation'
import { mutate } from 'swr'
import { petProjectApi } from '..'
import type { User, UserCreateInput } from '../types'
/**
* Создание пользователя с инвалидацией списка.
*/
export const useCreateUser = () => {
return useSWRMutation<User, Error, [string, string, string], UserCreateInput>(
['pet-project-api', 'user', 'create'],
(_key, { arg }) => petProjectApi.user.create(arg),
{
onSuccess: () => mutate(['pet-project-api', 'user', 'list']),
},
)
}'use client'
import { useCreateUser } from 'infrastructure/pet-project-api'
export function CreateUserForm() {
const { trigger, isMutating } = useCreateUser()
return (
<Form
onSubmit={(input) => trigger(input)}
disabled={isMutating}
/>
)
}В компоненте — снова только хук. Логика инвалидации кеша зашита внутрь, потребитель её не дублирует.
Передача config из компонента
Каждый хук принимает второй (или третий) параметр config?: SWRConfiguration — он пробрасывается в useSWR. Это даёт потребителю точечно настроить ревалидацию, fallbackData, suspense и т.п.:
'use client'
import { usePostDetail } from 'infrastructure/pet-project-api'
export function PostView({ slug, initialPost }: Props) {
const { data: post } = usePostDetail(slug, { fallbackData: initialPost })
// ...
}Начальное состояние с сервера
Если данные пришли из серверного компонента (см. Серверные компоненты) — передаются в fallbackData через config хука:
// page.tsx (server)
import { petProjectApi } from 'infrastructure/pet-project-api'
export default async function Page({ params }: { params: { slug: string } }) {
const initialPost = await petProjectApi.posts.get(params.slug)
return <PostView slug={params.slug} initialPost={initialPost} />
}// post-view.tsx ('use client')
import { usePostDetail } from 'infrastructure/pet-project-api'
export function PostView({ slug, initialPost }: Props) {
const { data: post } = usePostDetail(slug, { fallbackData: initialPost })
return <article>{post?.title}</article>
}Для массового заполнения кеша на странице с несколькими хуками — используется <SWRConfig fallback> обёртка. Серверный компонент собирает данные и передаёт сериализованную карту ключей в провайдер; все вложенные хуки сразу видят кеш.
Запрет прямых вызовов
// Плохо — прямой fetch в обход клиента
useEffect(() => {
fetch('/api/users').then(...)
}, [])
// Плохо — клиент без SWR: нет кеша, нет дедупликации
useEffect(() => {
petProjectApi.user.list().then(setUsers)
}, [])
// Плохо — useSWR в компоненте: SWR должен быть в хуке модуля
const { data } = useSWR(
['pet-project-api', 'user', 'list'],
() => petProjectApi.user.list(),
)
// Хорошо — готовый хук модуля
const { data } = useUserList()Если для нужной операции хука ещё нет — он добавляется в hooks/ модуля API, не в компонент.