docs: обновить структуру прикладных разделов
- перенесён раздел создания проекта в прикладные разделы - переработаны разделы REST-клиента и получения данных - удалена устаревшая категория работы с данными - обновлён стандартный конфиг Biome - добавлены правила для обязательного API baseUrl без fallback - обновлены сайдбар, карта документации, README и ссылки
This commit is contained in:
@@ -29,59 +29,53 @@ const sidebar = [
|
|||||||
{ text: 'Типизация', link: '/docs/basics/typing' },
|
{ text: 'Типизация', link: '/docs/basics/typing' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: 'Создание проекта',
|
|
||||||
items: [
|
|
||||||
{ text: 'Из шаблона', link: '/docs/creating-project/from-template' },
|
|
||||||
{ text: 'По гайду вручную', link: '/docs/creating-project/manual' },
|
|
||||||
{ text: 'Чистый Next.js', link: '/docs/creating-project/nextjs' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Работа с данными',
|
|
||||||
// collapsed: true,
|
|
||||||
items: [
|
|
||||||
{ text: 'Введение', link: '/docs/data/' },
|
|
||||||
{
|
|
||||||
text: 'REST',
|
|
||||||
collapsed: true,
|
|
||||||
items: [
|
|
||||||
{ text: 'Обзор', link: '/docs/data/rest/' },
|
|
||||||
{
|
|
||||||
text: 'Создание клиента',
|
|
||||||
collapsed: true,
|
|
||||||
items: [
|
|
||||||
{ text: 'Обзор', link: '/docs/data/rest/clients/' },
|
|
||||||
{ text: 'Автогенерация из OpenAPI', link: '/docs/data/rest/clients/auto' },
|
|
||||||
{ text: 'Ручное создание', link: '/docs/data/rest/clients/manual' },
|
|
||||||
{ text: 'GET-хуки REST-клиента', link: '/docs/data/rest/clients/hooks' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Использование',
|
|
||||||
collapsed: true,
|
|
||||||
items: [
|
|
||||||
{ text: 'Стратегии получения данных', link: '/docs/data/rest/strategies/' },
|
|
||||||
{ text: 'Серверный await', link: '/docs/data/rest/strategies/server-await' },
|
|
||||||
{ text: 'Параллельные серверные запросы', link: '/docs/data/rest/strategies/parallel-server-requests' },
|
|
||||||
{ text: 'Передача промиса ниже', link: '/docs/data/rest/strategies/pass-promise-down' },
|
|
||||||
{ text: 'Начальные данные для клиентских хуков', link: '/docs/data/rest/strategies/client-hooks-initial-data' },
|
|
||||||
{ text: 'Клиентский GET-хук', link: '/docs/data/rest/strategies/client-get-hook' },
|
|
||||||
{ text: 'Business-композиция', link: '/docs/data/rest/strategies/business-composition' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ text: 'Realtime', link: '/docs/data/realtime' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
text: 'Прикладные разделы',
|
text: 'Прикладные разделы',
|
||||||
items: [
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Создание проекта',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Из шаблона', link: '/docs/applied/creating-project/from-template' },
|
||||||
|
{ text: 'По гайду вручную', link: '/docs/applied/creating-project/manual' },
|
||||||
|
{ text: 'Чистый Next.js', link: '/docs/applied/creating-project/nextjs' },
|
||||||
|
],
|
||||||
|
},
|
||||||
{ text: 'Структура проекта', link: '/docs/applied/project-structure' },
|
{ text: 'Структура проекта', link: '/docs/applied/project-structure' },
|
||||||
{ text: 'Страницы', link: '/docs/applied/page-level' },
|
{ text: 'Страницы', link: '/docs/applied/page-level' },
|
||||||
{ text: 'Компонент', link: '/docs/applied/component' },
|
{ text: 'Компонент', link: '/docs/applied/component' },
|
||||||
{ text: 'Модуль', link: '/docs/applied/module' },
|
{ text: 'Модуль', link: '/docs/applied/module' },
|
||||||
|
{
|
||||||
|
text: 'REST-клиент',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Введение', link: '/docs/applied/rest-client/' },
|
||||||
|
{
|
||||||
|
text: 'Настройка',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Обзор', link: '/docs/applied/rest-client/setup/' },
|
||||||
|
{ text: 'Автогенерация из OpenAPI', link: '/docs/applied/rest-client/setup/auto' },
|
||||||
|
{ text: 'Ручное создание', link: '/docs/applied/rest-client/setup/manual' },
|
||||||
|
{ text: 'GET-хуки REST-клиента', link: '/docs/applied/rest-client/setup/hooks' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ text: 'Использование', link: '/docs/applied/rest-client/usage' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Получение данных',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Обзор', link: '/docs/applied/data-fetch/' },
|
||||||
|
{ text: 'Серверный await', link: '/docs/applied/data-fetch/server-await' },
|
||||||
|
{ text: 'Параллельные серверные запросы', link: '/docs/applied/data-fetch/parallel-server-requests' },
|
||||||
|
{ text: 'Передача промиса ниже', link: '/docs/applied/data-fetch/pass-promise-down' },
|
||||||
|
{ text: 'Начальные данные для клиентских хуков', link: '/docs/applied/data-fetch/client-hooks-initial-data' },
|
||||||
|
{ text: 'Клиентский GET-хук', link: '/docs/applied/data-fetch/client-get-hook' },
|
||||||
|
{ text: 'Business-композиция', link: '/docs/applied/data-fetch/business-composition' },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: 'Стили',
|
text: 'Стили',
|
||||||
collapsed: true,
|
collapsed: true,
|
||||||
|
|||||||
@@ -33,19 +33,23 @@ docs/
|
|||||||
│ ├── naming.md
|
│ ├── naming.md
|
||||||
│ ├── documentation.md
|
│ ├── documentation.md
|
||||||
│ └── typing.md
|
│ └── typing.md
|
||||||
|
└── applied/ # Прикладные разделы: настройка и использование
|
||||||
├── creating-project/ # Создание проекта: как поднять новый проект
|
├── creating-project/ # Создание проекта: как поднять новый проект
|
||||||
│ ├── from-template.md
|
│ ├── from-template.md
|
||||||
│ ├── manual.md
|
│ ├── manual.md
|
||||||
│ └── nextjs.md
|
│ └── nextjs.md
|
||||||
├── data/ # Работа с данными
|
|
||||||
│ ├── index.md
|
|
||||||
│ ├── realtime.md
|
|
||||||
│ └── rest/
|
|
||||||
└── applied/ # Прикладные разделы: настройка и использование
|
|
||||||
├── project-structure.md
|
├── project-structure.md
|
||||||
├── page-level.md
|
├── page-level.md
|
||||||
├── component.md
|
├── component.md
|
||||||
├── module.md
|
├── module.md
|
||||||
|
├── rest-client/ # REST-клиент: настройка клиента сервиса
|
||||||
|
│ ├── index.md
|
||||||
|
│ ├── usage.md
|
||||||
|
│ └── setup/
|
||||||
|
├── data-fetch/ # Получение данных: стратегии серверного и клиентского получения
|
||||||
|
│ ├── index.md
|
||||||
|
│ ├── server-await.md
|
||||||
|
│ └── client-get-hook.md
|
||||||
├── styles/ # Стили: настройка + использование
|
├── styles/ # Стили: настройка + использование
|
||||||
│ ├── styles-setup.md
|
│ ├── styles-setup.md
|
||||||
│ └── styles-usage.md
|
│ └── styles-usage.md
|
||||||
@@ -76,15 +80,14 @@ generate-llms.ts # Скрипт генерации llms.txt и R
|
|||||||
|
|
||||||
### Добавление нового раздела
|
### Добавление нового раздела
|
||||||
|
|
||||||
1. Создать `.md`-файл в нужной папке: `basics/`, `creating-project/`,
|
1. Создать `.md`-файл в нужной папке: `basics/` или `applied/`.
|
||||||
или `applied/`.
|
|
||||||
2. Добавить пункт в сайдбар — `.vitepress/config.ts`.
|
2. Добавить пункт в сайдбар — `.vitepress/config.ts`.
|
||||||
Сайдбар — единственный источник порядка и группировки для `llms.txt`.
|
Сайдбар — единственный источник порядка и группировки для `llms.txt`.
|
||||||
3. Запустить `npm run llms` для обновления `llms.txt` и README.
|
3. Запустить `npm run llms` для обновления `llms.txt` и README.
|
||||||
|
|
||||||
## Типы разделов
|
## Типы разделов
|
||||||
|
|
||||||
Документация разделена на четыре группы. Каждая отвечает на свой вопрос
|
Документация разделена на две основные группы. Каждая отвечает на свой вопрос
|
||||||
и имеет свою природу — это влияет на содержимое и структуру страницы.
|
и имеет свою природу — это влияет на содержимое и структуру страницы.
|
||||||
|
|
||||||
### Базовые правила (`basics/`)
|
### Базовые правила (`basics/`)
|
||||||
@@ -101,30 +104,25 @@ generate-llms.ts # Скрипт генерации llms.txt и R
|
|||||||
**Граница:** если правило касается только одной области (только стили,
|
**Граница:** если правило касается только одной области (только стили,
|
||||||
только компоненты, только API) — оно живёт в прикладном разделе, не в базовых.
|
только компоненты, только API) — оно живёт в прикладном разделе, не в базовых.
|
||||||
|
|
||||||
### Создание проекта (`creating-project/`)
|
|
||||||
|
|
||||||
**Отвечает на вопрос:** «Как поднять новый проект?»
|
|
||||||
|
|
||||||
Сценарии запуска нового проекта целиком: из шаблона, вручную, чистая
|
|
||||||
установка фреймворка. Раздел описывает порядок шагов на уровне всего
|
|
||||||
проекта; детали отдельных инструментов лежат в `applied/`.
|
|
||||||
|
|
||||||
**Граница:** не дублирует разделы `applied/`. Ссылается на них как на
|
|
||||||
шаги в общем сценарии.
|
|
||||||
|
|
||||||
### Прикладные разделы (`applied/`)
|
### Прикладные разделы (`applied/`)
|
||||||
|
|
||||||
**Отвечает на вопрос:** «Как поставить инструмент и как им пользоваться?»
|
**Отвечает на вопрос:** «Как решить практическую задачу в проекте?»
|
||||||
|
|
||||||
Прикладные разделы объединяют настройку и использование инструментов
|
Прикладные разделы объединяют создание проекта, настройку и использование инструментов
|
||||||
и подсистем. Каждый раздел — самостоятельная предметная область.
|
и подсистем. Каждый раздел — самостоятельная предметная область.
|
||||||
|
|
||||||
Разделы делятся на два типа:
|
`applied/creating-project/` отвечает на вопрос «Как поднять новый проект?» и описывает сценарии запуска нового проекта целиком: из шаблона, вручную, чистую установку фреймворка. Детали отдельных инструментов остаются в собственных прикладных разделах, а создание проекта ссылается на них как на шаги общего сценария.
|
||||||
|
|
||||||
1. **Только настройка** — разовая установка инструмента (линтер,
|
Разделы делятся на три типа:
|
||||||
|
|
||||||
|
1. **Создание проекта** — сценарии запуска проекта целиком. Живут в
|
||||||
|
`applied/creating-project/` и в сайдбаре оформляются как первая группа
|
||||||
|
прикладных разделов.
|
||||||
|
|
||||||
|
2. **Только настройка** — разовая установка инструмента (линтер,
|
||||||
CSS-процессор, алиасы). Файл без суффикса: `biome.md`, `postcss.md`.
|
CSS-процессор, алиасы). Файл без суффикса: `biome.md`, `postcss.md`.
|
||||||
|
|
||||||
2. **Настройка + использование** — область, требующая и установки,
|
3. **Настройка + использование** — область, требующая и установки,
|
||||||
и повседневных правил. Два файла с суффиксами: `styles-setup.md`
|
и повседневных правил. Два файла с суффиксами: `styles-setup.md`
|
||||||
(настройка) и `styles-usage.md` (использование). В сайдбаре
|
(настройка) и `styles-usage.md` (использование). В сайдбаре
|
||||||
оборачиваются в collapsed-группу.
|
оборачиваются в collapsed-группу.
|
||||||
@@ -136,7 +134,7 @@ generate-llms.ts # Скрипт генерации llms.txt и R
|
|||||||
## Структура прикладного раздела
|
## Структура прикладного раздела
|
||||||
|
|
||||||
Шаблон ниже относится к usage-страницам прикладных разделов (`applied/*-usage.md`).
|
Шаблон ниже относится к usage-страницам прикладных разделов (`applied/*-usage.md`).
|
||||||
Setup-страницы (`applied/*-setup.md`) и `creating-project/` имеют другую
|
Setup-страницы (`applied/*-setup.md`) и `applied/creating-project/` имеют другую
|
||||||
структуру — ориентированную на пошаговую установку (требования → установка →
|
структуру — ориентированную на пошаговую установку (требования → установка →
|
||||||
проверка).
|
проверка).
|
||||||
|
|
||||||
@@ -260,7 +258,7 @@ description: Описание раздела одним предложением
|
|||||||
- Исключение: имя инструмента допустимо, если оно — единственное
|
- Исключение: имя инструмента допустимо, если оно — единственное
|
||||||
устойчивое имя самой области (`PostCSS`, `Biome`, `VS Code`).
|
устойчивое имя самой области (`PostCSS`, `Biome`, `VS Code`).
|
||||||
- Если страница вложена в семантическую группу
|
- Если страница вложена в семантическую группу
|
||||||
(`Архитектура → Слои`, `Данные → REST → Серверные компоненты`)
|
(`Архитектура → Слои`, `Прикладные → REST-клиент → Использование`)
|
||||||
и короткое имя теряет смысл при прямой ссылке — `h1` поднимает
|
и короткое имя теряет смысл при прямой ссылке — `h1` поднимает
|
||||||
имя родителя в заголовок: `Слои SLM`, `Сегменты SLM`. В сайдбаре
|
имя родителя в заголовок: `Слои SLM`, `Сегменты SLM`. В сайдбаре
|
||||||
допустимо оставить короткий вариант (`Слои`, `Сегменты`) — там
|
допустимо оставить короткий вариант (`Слои`, `Сегменты`) — там
|
||||||
@@ -309,7 +307,7 @@ description: Описание раздела одним предложением
|
|||||||
- Звучит как ответ человека другу, а не как техспек.
|
- Звучит как ответ человека другу, а не как техспек.
|
||||||
- Описание читается **самостоятельно**, без контекста сайдбара.
|
- Описание читается **самостоятельно**, без контекста сайдбара.
|
||||||
- Если страница вложена в семантическую группу
|
- Если страница вложена в семантическую группу
|
||||||
(например, `Данные → REST → Клиенты → ...`) и её заголовок
|
(например, `Прикладные → REST-клиент → Настройка → ...`) и её заголовок
|
||||||
без этой группы теряет смысл — описание явно содержит имя
|
без этой группы теряет смысл — описание явно содержит имя
|
||||||
родительской области, чтобы читалось без сайдбара.
|
родительской области, чтобы читалось без сайдбара.
|
||||||
|
|
||||||
|
|||||||
28
README.md
28
README.md
@@ -37,22 +37,15 @@
|
|||||||
| Документирование | Как писать JSDoc: что документировать, а что нет? |
|
| Документирование | Как писать JSDoc: что документировать, а что нет? |
|
||||||
| Типизация | Как типизировать: type vs interface, any/unknown? |
|
| Типизация | Как типизировать: type vs interface, any/unknown? |
|
||||||
|
|
||||||
### Создание проекта
|
|
||||||
|
|
||||||
**Как начать новый проект** — варианты установки и эталонный набор инструментов.
|
|
||||||
|
|
||||||
| Раздел | Отвечает на вопрос |
|
|
||||||
|--------|-------------------|
|
|
||||||
| Создание проекта из шаблона | Как начать проект из готового шаблона? |
|
|
||||||
| Создание проекта вручную | Как поднять проект с нуля без шаблона? |
|
|
||||||
| Чистая установка Next.js | Как поставить голый Next.js под дальнейшую сборку? |
|
|
||||||
|
|
||||||
### Настройка
|
### Настройка
|
||||||
|
|
||||||
**Как сконфигурировать проект** — пошаговая настройка инструментов и инфраструктуры.
|
**Как сконфигурировать проект** — пошаговая настройка инструментов и инфраструктуры.
|
||||||
|
|
||||||
| Раздел | Отвечает на вопрос |
|
| Раздел | Отвечает на вопрос |
|
||||||
|--------|-------------------|
|
|--------|-------------------|
|
||||||
|
| Создание проекта из шаблона | Как начать проект из готового шаблона? |
|
||||||
|
| Создание проекта вручную | Как поднять проект с нуля без шаблона? |
|
||||||
|
| Чистая установка Next.js | Как поставить голый Next.js под дальнейшую сборку? |
|
||||||
| Алиасы импортов | Как настроить алиасы импортов? |
|
| Алиасы импортов | Как настроить алиасы импортов? |
|
||||||
| Biome | Как настроить линтер и форматтер? |
|
| Biome | Как настроить линтер и форматтер? |
|
||||||
| PostCSS | Какие плагины PostCSS нужны и как их настроить? |
|
| PostCSS | Какие плагины PostCSS нужны и как их настроить? |
|
||||||
@@ -70,6 +63,8 @@
|
|||||||
| Структура проекта | Как организованы папки и файлы по SLM? |
|
| Структура проекта | Как организованы папки и файлы по SLM? |
|
||||||
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
|
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
|
||||||
| Файлы роутинга | Как описывать layout, page, loading, error, not-found? |
|
| Файлы роутинга | Как описывать layout, page, loading, error, not-found? |
|
||||||
|
| REST-клиент | Как настроить клиент внешнего REST API? |
|
||||||
|
| Получение данных | Как выбрать способ получения данных под рендер страницы? |
|
||||||
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
|
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
|
||||||
| Стили | Как писать CSS: вложенность, медиа, токены? |
|
| Стили | Как писать CSS: вложенность, медиа, токены? |
|
||||||
| SVG-спрайты | Как использовать SVG-спрайты в коде? |
|
| SVG-спрайты | Как использовать SVG-спрайты в коде? |
|
||||||
@@ -79,16 +74,3 @@
|
|||||||
| Хуки | _(не заполнен)_ |
|
| Хуки | _(не заполнен)_ |
|
||||||
| Шрифты | _(не заполнен)_ |
|
| Шрифты | _(не заполнен)_ |
|
||||||
| Локализация | _(не заполнен)_ |
|
| Локализация | _(не заполнен)_ |
|
||||||
|
|
||||||
### Данные
|
|
||||||
|
|
||||||
**Как работать с источниками данных** — REST, realtime и потребление в компонентах.
|
|
||||||
|
|
||||||
| Раздел | Отвечает на вопрос |
|
|
||||||
|--------|-------------------|
|
|
||||||
| Источники данных | Как устроена работа с данными в проекте? |
|
|
||||||
| REST: Автоматическая генерация | Как сгенерировать REST-клиент автоматически из OpenAPI? |
|
|
||||||
| REST: Ручное создание | Как написать REST-клиент вручную? |
|
|
||||||
| REST: Серверные компоненты | Как получать данные в серверных компонентах? |
|
|
||||||
| REST: Клиентские компоненты | Как получать данные в клиентских компонентах? |
|
|
||||||
| Realtime | Как работать с realtime-каналами и сокетами? |
|
|
||||||
|
|||||||
@@ -19,35 +19,28 @@
|
|||||||
- [Документирование](./basics/documentation.md) — Что и как документировать в коде.
|
- [Документирование](./basics/documentation.md) — Что и как документировать в коде.
|
||||||
- [Типизация](./basics/typing.md) — Как типизируется код в проекте.
|
- [Типизация](./basics/typing.md) — Как типизируется код в проекте.
|
||||||
|
|
||||||
## Создание проекта
|
|
||||||
|
|
||||||
- [Из шаблона](./creating-project/from-template.md) — Создание нового проекта на основе готового шаблона.
|
|
||||||
- [По гайду вручную](./creating-project/manual.md) — Поэтапное создание нового проекта без использования шаблона.
|
|
||||||
- [Чистый Next.js](./creating-project/nextjs.md) — Установка Next.js без лишнего шаблона — голый каркас под дальнейшую сборку.
|
|
||||||
|
|
||||||
## Работа с данными
|
|
||||||
|
|
||||||
- [Введение](./data/index.md) — Какие источники данных используются в проекте и как с ними работать.
|
|
||||||
- [REST](./data/rest/index.md) — Как правильно работать с REST API в проекте.
|
|
||||||
- [REST: Создание клиента](./data/rest/clients/index.md) — Как выбрать способ создания REST-клиента и где размещать его части.
|
|
||||||
- [REST: Создание клиента: Автогенерация из OpenAPI](./data/rest/clients/auto.md) — Генерация REST-клиента из OpenAPI-спецификации через @gromlab/api-codegen.
|
|
||||||
- [REST: Создание клиента: Ручное создание](./data/rest/clients/manual.md) — Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
|
|
||||||
- [REST: Создание клиента: GET-хуки REST-клиента](./data/rest/clients/hooks.md) — Прозрачные SWR-обёртки над GET-методами REST-клиента.
|
|
||||||
- [REST: Использование: Стратегии получения данных](./data/rest/strategies/index.md) — Как выбрать способ получения REST-данных в зависимости от места и сценария.
|
|
||||||
- [REST: Использование: Серверный await](./data/rest/strategies/server-await.md) — Получение REST-данных на сервере прямым await метода клиента.
|
|
||||||
- [REST: Использование: Параллельные серверные запросы](./data/rest/strategies/parallel-server-requests.md) — Как запускать независимые REST-запросы на сервере без waterfall.
|
|
||||||
- [REST: Использование: Передача промиса ниже](./data/rest/strategies/pass-promise-down.md) — Как запускать серверный REST-запрос выше и ожидать его во вложенном server-компоненте.
|
|
||||||
- [REST: Использование: Начальные данные для клиентских хуков](./data/rest/strategies/client-hooks-initial-data.md) — Как передать серверный промис в SWR fallback, чтобы клиентские GET-хуки получили начальные данные.
|
|
||||||
- [REST: Использование: Клиентский GET-хук](./data/rest/strategies/client-get-hook.md) — Получение REST-данных в Client Components через готовые GET-хуки REST-клиента.
|
|
||||||
- [REST: Использование: Business-композиция](./data/rest/strategies/business-composition.md) — Когда REST-данные нужно объединить или интерпретировать в бизнес-модуле.
|
|
||||||
- [Realtime](./data/realtime.md) — Работа с push-данными от сервера: подписки и события.
|
|
||||||
|
|
||||||
## Прикладные разделы
|
## Прикладные разделы
|
||||||
|
|
||||||
|
- [Создание проекта: Из шаблона](./applied/creating-project/from-template.md) — Создание нового проекта на основе готового шаблона.
|
||||||
|
- [Создание проекта: По гайду вручную](./applied/creating-project/manual.md) — Поэтапное создание нового проекта без использования шаблона.
|
||||||
|
- [Создание проекта: Чистый Next.js](./applied/creating-project/nextjs.md) — Установка Next.js без лишнего шаблона — голый каркас под дальнейшую сборку.
|
||||||
- [Структура проекта](./applied/project-structure.md) — Из чего состоит проект и где что лежит.
|
- [Структура проекта](./applied/project-structure.md) — Из чего состоит проект и где что лежит.
|
||||||
- [Страницы](./applied/page-level.md) — Как работать со страницами и другими файлами роутинга Next.js App Router.
|
- [Страницы](./applied/page-level.md) — Как работать со страницами и другими файлами роутинга Next.js App Router.
|
||||||
- [Компонент](./applied/component.md) — Как создавать React-компоненты внутри SLM-модулей.
|
- [Компонент](./applied/component.md) — Как создавать React-компоненты внутри SLM-модулей.
|
||||||
- [Модуль](./applied/module.md) — Как создавать и организовывать SLM-модули в проекте.
|
- [Модуль](./applied/module.md) — Как создавать и организовывать SLM-модули в проекте.
|
||||||
|
- [REST-клиент](./applied/rest-client/index.md) — Настройка REST-клиента сервиса для работы с внешним API.
|
||||||
|
- [REST-клиент: Настройка REST-клиента](./applied/rest-client/setup/index.md) — Из чего состоит REST-клиент и что подготовить перед использованием API.
|
||||||
|
- [REST-клиент: Автогенерация REST-клиента](./applied/rest-client/setup/auto.md) — Генерация REST-клиента из OpenAPI-спецификации.
|
||||||
|
- [REST-клиент: Ручное создание REST-клиента](./applied/rest-client/setup/manual.md) — Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
|
||||||
|
- [REST-клиент: GET-хуки REST-клиента](./applied/rest-client/setup/hooks.md) — Прозрачные SWR-обёртки над GET-методами REST-клиента.
|
||||||
|
- [REST-клиент: Использование REST-клиента](./applied/rest-client/usage.md) — Как вызывать готовый REST-клиент в серверном коде и submit-функциях.
|
||||||
|
- [Получение данных](./applied/data-fetch/index.md) — Как получать данные с учётом рендера страницы.
|
||||||
|
- [Получение данных: Серверный await](./applied/data-fetch/server-await.md) — Получение REST-данных на сервере до первого HTML.
|
||||||
|
- [Получение данных: Параллельные серверные запросы](./applied/data-fetch/parallel-server-requests.md) — Как запускать независимые REST-запросы на сервере без waterfall.
|
||||||
|
- [Получение данных: Передача промиса ниже](./applied/data-fetch/pass-promise-down.md) — Как запускать серверный REST-запрос выше и ожидать его во вложенном server-компоненте.
|
||||||
|
- [Получение данных: Начальные данные для клиентских хуков](./applied/data-fetch/client-hooks-initial-data.md) — Как дать клиентским GET-хукам начальные REST-данные.
|
||||||
|
- [Получение данных: Клиентский GET-хук](./applied/data-fetch/client-get-hook.md) — Получение REST-данных в Client Components через готовые GET-хуки REST-клиента.
|
||||||
|
- [Получение данных: Business-композиция](./applied/data-fetch/business-composition.md) — Когда REST-данные нужно объединить или интерпретировать в бизнес-модуле.
|
||||||
- [Стили: Настройка](./applied/styles/styles-setup.md) — Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили.
|
- [Стили: Настройка](./applied/styles/styles-setup.md) — Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили.
|
||||||
- [Стили: Использование](./applied/styles/styles-usage.md) — Как пишутся стили в проекте.
|
- [Стили: Использование](./applied/styles/styles-usage.md) — Как пишутся стили в проекте.
|
||||||
- [SVG-спрайты](./applied/svg-sprites/svg-sprites-intro.md) — Что такое SVG-спрайты и какие проблемы они решают.
|
- [SVG-спрайты](./applied/svg-sprites/svg-sprites-intro.md) — Что такое SVG-спрайты и какие проблемы они решают.
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ keywords: [biome, линтер, форматтер, lint, format, biome.json, "@
|
|||||||
|
|
||||||
В корне появится `biome.json` с дефолтными настройками.
|
В корне появится `biome.json` с дефолтными настройками.
|
||||||
|
|
||||||
3. Привести `biome.json` к стандартному виду — добавить override для `*.css` (см. «Стандартный `biome.json`»). Делается сразу после `init`, до первого запуска `lint`/`check`.
|
3. Привести `biome.json` к стандартному виду (см. «Стандартный `biome.json`»). Делается сразу после `init`, до первого запуска `lint`/`check`.
|
||||||
|
|
||||||
4. Добавить скрипты в `package.json`:
|
4. Добавить скрипты в `package.json`:
|
||||||
|
|
||||||
@@ -51,30 +51,63 @@ keywords: [biome, линтер, форматтер, lint, format, biome.json, "@
|
|||||||
|
|
||||||
## Стандартный `biome.json`
|
## Стандартный `biome.json`
|
||||||
|
|
||||||
Дефолтный `biome.json`, созданный `biome init`, кастомизируется ровно одним блоком — `overrides` для `*.css` с отключённым правилом `suspicious/noUnknownAtRules`. Этот override **обязателен по умолчанию во всех проектах**, независимо от того, подключены ли уже стили: проектный CSS-стек использует `@custom-media` и другие нестандартные at-правила, которые Biome не распознаёт; без override `npm run lint` падает.
|
Дефолтный `biome.json`, созданный `biome init`, заменяется стандартным конфигом проекта.
|
||||||
|
|
||||||
Фрагмент, который добавляется в `biome.json`:
|
Стандартный `biome.json`:
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
"overrides": [
|
"$schema": "https://biomejs.dev/schemas/2.2.0/schema.json",
|
||||||
{
|
"vcs": {
|
||||||
"includes": ["**/*.css"],
|
"enabled": true,
|
||||||
|
"clientKind": "git",
|
||||||
|
"useIgnoreFile": true
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"ignoreUnknown": true,
|
||||||
|
"includes": ["**", "!node_modules", "!.next", "!dist", "!build", "!.templates", "!src/infra/**/generated"]
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "space",
|
||||||
|
"indentWidth": 2,
|
||||||
|
"lineWidth": 120
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"quoteStyle": "single",
|
||||||
|
"jsxQuoteStyle": "double"
|
||||||
|
}
|
||||||
|
},
|
||||||
"linter": {
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"recommended": true,
|
||||||
"suspicious": {
|
"suspicious": {
|
||||||
"noUnknownAtRules": "off"
|
"noUnknownAtRules": "off"
|
||||||
|
},
|
||||||
|
"correctness": {
|
||||||
|
"noUnknownMediaFeatureName": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domains": {
|
||||||
|
"next": "recommended",
|
||||||
|
"react": "recommended"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"assist": {
|
||||||
|
"actions": {
|
||||||
|
"source": {
|
||||||
|
"organizeImports": "on"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Если в `biome.json` уже есть массив `overrides` — добавить элемент в него; не дублировать массив.
|
`src/infra/**/generated` исключается из Biome, потому что generated-файлы не правятся руками. При этом generated-файлы остаются в git.
|
||||||
|
|
||||||
Прочая настройка правил Biome — отдельная задача, не входит в стандартный канон.
|
Правила `suspicious/noUnknownAtRules` и `correctness/noUnknownMediaFeatureName` отключены, потому что проектный CSS-стек использует `@custom-media` и другие конструкции, которые Biome может не распознавать.
|
||||||
|
|
||||||
## Интеграция с VS Code
|
## Интеграция с VS Code
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ keywords: [создать проект из шаблона, шаблон, templa
|
|||||||
|
|
||||||
- **Стек:** Next.js (App Router), TypeScript, React.
|
- **Стек:** Next.js (App Router), TypeScript, React.
|
||||||
- **Архитектура:** структура папок по SLM, алиасы импортов.
|
- **Архитектура:** структура папок по SLM, алиасы импортов.
|
||||||
- **Качество кода:** Biome (линтер и форматер), настройки VS Code.
|
- **Качество кода:** Biome (линтер и форматтер), настройки VS Code.
|
||||||
- **Стили:** PostCSS Modules с плагинами, токены, медиа-брейкпоинты.
|
- **Стили:** PostCSS Modules с плагинами, токены, медиа-брейкпоинты.
|
||||||
- **Ассеты:** генерация SVG-спрайтов.
|
- **Ассеты:** генерация SVG-спрайтов.
|
||||||
- **Кодогенерация:** шаблоны для страниц, компонентов, хуков, сторов.
|
- **Кодогенерация:** шаблоны для страниц, компонентов, хуков, сторов.
|
||||||
в
|
|
||||||
## Установка
|
## Установка
|
||||||
|
|
||||||
1. Склонировать шаблон в родительском каталоге будущего проекта:
|
1. Склонировать шаблон в родительском каталоге будущего проекта:
|
||||||
@@ -12,7 +12,7 @@ keywords: [создать проект, новый проект, с нуля, in
|
|||||||
|
|
||||||
| Компонент | Роль | Раздел |
|
| Компонент | Роль | Раздел |
|
||||||
|-----------|------|--------|
|
|-----------|------|--------|
|
||||||
| Next.js | Фреймворк (роутинг, сборка, SSR) | [Next.js](/docs/creating-project/nextjs) |
|
| Next.js | Фреймворк (роутинг, сборка, SSR) | [Next.js](/docs/applied/creating-project/nextjs) |
|
||||||
| Алиасы | Импорты по слоям SLM | [Алиасы](/docs/applied/aliases) |
|
| Алиасы | Импорты по слоям SLM | [Алиасы](/docs/applied/aliases) |
|
||||||
| Biome | Линтер и форматтер (замена ESLint + Prettier) | [Biome](/docs/applied/biome) |
|
| Biome | Линтер и форматтер (замена ESLint + Prettier) | [Biome](/docs/applied/biome) |
|
||||||
| Стили | Глобальные токены и breakpoints | [Стили](/docs/applied/styles/styles-setup) |
|
| Стили | Глобальные токены и breakpoints | [Стили](/docs/applied/styles/styles-setup) |
|
||||||
@@ -37,7 +37,7 @@ keywords: [создать проект, новый проект, с нуля, in
|
|||||||
|
|
||||||
Скелет фреймворка — обязательный первый шаг, остальное опирается на него.
|
Скелет фреймворка — обязательный первый шаг, остальное опирается на него.
|
||||||
|
|
||||||
См. [Next.js](/docs/creating-project/nextjs). После выполнения проверки этого раздела `npm run build` должен проходить.
|
См. [Next.js](/docs/applied/creating-project/nextjs). После выполнения проверки этого раздела `npm run build` должен проходить.
|
||||||
|
|
||||||
### 2. Алиасы
|
### 2. Алиасы
|
||||||
|
|
||||||
@@ -21,13 +21,13 @@ Business-композиция используется, когда просто
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/business/pets/hooks/use-available-pets.hook.ts
|
// src/business/pets/hooks/use-available-pets.hook.ts
|
||||||
import { useGetPetList } from 'infra/pet-store-api'
|
import { StatusEnum, useGetPetList } from 'infra/pet-store-api'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Доменный список доступных питомцев.
|
* Доменный список доступных питомцев.
|
||||||
*/
|
*/
|
||||||
export const useAvailablePets = () => {
|
export const useAvailablePets = () => {
|
||||||
const query = useGetPetList('available')
|
const query = useGetPetList({ status: StatusEnum.Available })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...query,
|
...query,
|
||||||
@@ -42,15 +42,15 @@ export const useAvailablePets = () => {
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/business/pets/hooks/use-pets-dashboard.hook.ts
|
// src/business/pets/hooks/use-pets-dashboard.hook.ts
|
||||||
import { useGetPetList } from 'infra/pet-store-api'
|
import { StatusEnum, useGetPetList } from 'infra/pet-store-api'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Данные dashboard по питомцам.
|
* Данные dashboard по питомцам.
|
||||||
*/
|
*/
|
||||||
export const usePetsDashboard = () => {
|
export const usePetsDashboard = () => {
|
||||||
const availablePets = useGetPetList('available')
|
const availablePets = useGetPetList({ status: StatusEnum.Available })
|
||||||
const pendingPets = useGetPetList('pending')
|
const pendingPets = useGetPetList({ status: StatusEnum.Pending })
|
||||||
const soldPets = useGetPetList('sold')
|
const soldPets = useGetPetList({ status: StatusEnum.Sold })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
availablePets,
|
availablePets,
|
||||||
@@ -108,7 +108,7 @@ src/business/
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
// Плохо — business-смысл внутри infra-хука
|
// Плохо — business-смысл внутри infra-хука
|
||||||
export const useGetPetList = (status: PetStatus) => {
|
export const useGetPetList = (params?: FindPetsByStatusParams | null) => {
|
||||||
const query = useSWR(...)
|
const query = useSWR(...)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -21,14 +21,13 @@ keywords: [rest, client components, swr, get-хук, client state]
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useGetPetList } from 'infra/pet-store-api'
|
import { StatusEnum, useGetPetList } from 'infra/pet-store-api'
|
||||||
import type { PetStatus } from 'infra/pet-store-api'
|
|
||||||
|
|
||||||
const statuses: PetStatus[] = ['available', 'pending', 'sold']
|
const statuses = [StatusEnum.Available, StatusEnum.Pending, StatusEnum.Sold]
|
||||||
|
|
||||||
export function PetTabs() {
|
export function PetTabs() {
|
||||||
const [status, setStatus] = useState<PetStatus>('available')
|
const [status, setStatus] = useState(StatusEnum.Available)
|
||||||
const { data: pets, isLoading, error } = useGetPetList(status)
|
const { data: pets, isLoading, error } = useGetPetList({ status })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
@@ -75,7 +74,7 @@ useEffect(() => {
|
|||||||
|
|
||||||
// Плохо — useSWR в компоненте
|
// Плохо — useSWR в компоненте
|
||||||
const { data } = useSWR(
|
const { data } = useSWR(
|
||||||
['pet-store-api', 'pet', 'list', status],
|
['pet-store-api', `/pet/findByStatus?status=${status}`],
|
||||||
() => petStoreApi.pet.findPetsByStatus({ status }),
|
() => petStoreApi.pet.findPetsByStatus({ status }),
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
@@ -84,6 +83,6 @@ const { data } = useSWR(
|
|||||||
|
|
||||||
## Когда выбрать другую стратегию
|
## Когда выбрать другую стратегию
|
||||||
|
|
||||||
- Данные нужны до первого HTML — [Серверный await](/docs/data/rest/strategies/server-await).
|
- Данные нужны до первого HTML — [Серверный await](/docs/applied/data-fetch/server-await).
|
||||||
- Клиентский хук должен получить начальные данные сразу — [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data).
|
- Клиентский хук должен получить начальные данные сразу — [Начальные данные для клиентских хуков](/docs/applied/data-fetch/client-hooks-initial-data).
|
||||||
- Нужно вычислить бизнес-состояние — [Business-композиция](/docs/data/rest/strategies/business-composition).
|
- Нужно вычислить бизнес-состояние — [Business-композиция](/docs/applied/data-fetch/business-composition).
|
||||||
@@ -31,8 +31,13 @@ keywords: [rest, swr, fallback, initial data, client hooks, unstable_serialize,
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infra/pet-store-api/hooks/use-get-pet-list.hook.ts
|
// src/infra/pet-store-api/hooks/use-get-pet-list.hook.ts
|
||||||
export const getPetListKey = (status: PetStatus) =>
|
export const getPetListKey = (params?: FindPetsByStatusParams | null) => {
|
||||||
['pet-store-api', 'pet', 'list', status] as const
|
if (!params?.status) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['pet-store-api', `/pet/findByStatus?status=${params.status}`] as const
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Ключ экспортируется из REST-модуля, потому что он нужен и GET-хуку, и серверному `SWRConfig fallback`.
|
Ключ экспортируется из REST-модуля, потому что он нужен и GET-хуку, и серверному `SWRConfig fallback`.
|
||||||
@@ -46,6 +51,7 @@ import { SWRConfig, unstable_serialize } from 'swr'
|
|||||||
import {
|
import {
|
||||||
getPetListKey,
|
getPetListKey,
|
||||||
petStoreApi,
|
petStoreApi,
|
||||||
|
StatusEnum,
|
||||||
} from 'infra/pet-store-api'
|
} from 'infra/pet-store-api'
|
||||||
|
|
||||||
type PetsLayoutProps = {
|
type PetsLayoutProps = {
|
||||||
@@ -53,15 +59,14 @@ type PetsLayoutProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function PetsLayout({ children }: PetsLayoutProps) {
|
export default async function PetsLayout({ children }: PetsLayoutProps) {
|
||||||
const availablePetsPromise = petStoreApi.pet.findPetsByStatus({
|
const params = { status: StatusEnum.Available }
|
||||||
status: 'available',
|
const availablePetsPromise = petStoreApi.pet.findPetsByStatus(params)
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SWRConfig
|
<SWRConfig
|
||||||
value={{
|
value={{
|
||||||
fallback: {
|
fallback: {
|
||||||
[unstable_serialize(getPetListKey('available'))]: availablePetsPromise,
|
[unstable_serialize(getPetListKey(params))]: availablePetsPromise,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -78,10 +83,12 @@ export default async function PetsLayout({ children }: PetsLayoutProps) {
|
|||||||
```tsx
|
```tsx
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useGetPetList } from 'infra/pet-store-api'
|
import { StatusEnum, useGetPetList } from 'infra/pet-store-api'
|
||||||
|
|
||||||
export function PetList() {
|
export function PetList() {
|
||||||
const { data: pets, isLoading } = useGetPetList('available')
|
const { data: pets, isLoading } = useGetPetList({
|
||||||
|
status: StatusEnum.Available,
|
||||||
|
})
|
||||||
|
|
||||||
if (isLoading) return <div>Загрузка...</div>
|
if (isLoading) return <div>Загрузка...</div>
|
||||||
|
|
||||||
@@ -100,10 +107,11 @@ export function PetList() {
|
|||||||
## Что важно
|
## Что важно
|
||||||
|
|
||||||
- Ключ `fallback` должен совпадать с ключом GET-хука.
|
- Ключ `fallback` должен совпадать с ключом GET-хука.
|
||||||
|
- `fallback` использует ту же key-функцию и те же params, что и GET-хук.
|
||||||
- Серверный код вызывает метод клиента, а не GET-хук.
|
- Серверный код вызывает метод клиента, а не GET-хук.
|
||||||
- Клиентский компонент вызывает GET-хук, а не `useSWR` напрямую.
|
- Клиентский компонент вызывает GET-хук, а не `useSWR` напрямую.
|
||||||
- Эта стратегия не означает ручную работу с кешем в компонентах.
|
- Эта стратегия не означает ручную работу с кешем в компонентах.
|
||||||
|
|
||||||
## Когда не использовать
|
## Когда не использовать
|
||||||
|
|
||||||
Если данные нужны только серверному компоненту, используйте [Серверный await](/docs/data/rest/strategies/server-await). Если данные зависят от состояния браузера, используйте [Клиентский GET-хук](/docs/data/rest/strategies/client-get-hook).
|
Если данные нужны только серверному компоненту, используйте [Серверный await](/docs/applied/data-fetch/server-await). Если данные зависят от состояния браузера, используйте [Клиентский GET-хук](/docs/applied/data-fetch/client-get-hook).
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
---
|
---
|
||||||
title: Стратегии получения данных
|
title: Получение данных
|
||||||
description: Как выбрать получение REST-данных с учётом рендера страницы.
|
description: Как получать данные с учётом рендера страницы.
|
||||||
keywords: [rest, стратегии, render, isr, ssr, server components, swr, promise, business]
|
keywords: [rest, стратегии, render, isr, ssr, server components, swr, promise, business]
|
||||||
---
|
---
|
||||||
|
|
||||||
# Стратегии получения данных
|
# Получение данных
|
||||||
|
|
||||||
Как выбрать получение REST-данных с учётом рендера страницы.
|
Как получать данные с учётом рендера страницы.
|
||||||
|
|
||||||
Перед выбором стратегии должен быть создан REST-клиент сервиса. Если клиента ещё нет, начните с раздела [Создание клиента](/docs/data/rest/clients/).
|
Перед выбором стратегии должен быть настроен REST-клиент сервиса. Если клиента ещё нет, начните с раздела [Настройка REST-клиента](/docs/applied/rest-client/setup/).
|
||||||
|
|
||||||
## Сначала определите рендер страницы
|
## Сначала определите рендер страницы
|
||||||
|
|
||||||
@@ -52,12 +52,12 @@ SSR/dynamic rendering выбирается только когда данные
|
|||||||
|
|
||||||
| Ситуация после выбора рендера | Стратегия | Где читать |
|
| Ситуация после выбора рендера | Стратегия | Где читать |
|
||||||
|-------------------------------|-----------|------------|
|
|-------------------------------|-----------|------------|
|
||||||
| Данные обязательны для первого HTML, SEO, `notFound()` или `redirect()` | Серверный `await` | [Серверный await](/docs/data/rest/strategies/server-await) |
|
| Данные обязательны для первого HTML, SEO, `notFound()` или `redirect()` | Серверный `await` | [Серверный await](/docs/applied/data-fetch/server-await) |
|
||||||
| Несколько независимых данных нужны до рендера | Запуск промисов + `Promise.all` | [Параллельные серверные запросы](/docs/data/rest/strategies/parallel-server-requests) |
|
| Несколько независимых данных нужны до рендера | Запуск промисов + `Promise.all` | [Параллельные серверные запросы](/docs/applied/data-fetch/parallel-server-requests) |
|
||||||
| Часть UI можно загрузить отдельно | Передача промиса ниже + `Suspense` | [Передача промиса ниже](/docs/data/rest/strategies/pass-promise-down) |
|
| Часть UI можно загрузить отдельно | Передача промиса ниже + `Suspense` | [Передача промиса ниже](/docs/applied/data-fetch/pass-promise-down) |
|
||||||
| Client Component должен получить данные сразу из SWR | Начальные данные для клиентских хуков | [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data) |
|
| Client Component должен получить данные сразу из SWR | Начальные данные для клиентских хуков | [Начальные данные для клиентских хуков](/docs/applied/data-fetch/client-hooks-initial-data) |
|
||||||
| Данные зависят от client state | Клиентский GET-хук | [Клиентский GET-хук](/docs/data/rest/strategies/client-get-hook) |
|
| Данные зависят от client state | Клиентский GET-хук | [Клиентский GET-хук](/docs/applied/data-fetch/client-get-hook) |
|
||||||
| Нужно объединить несколько запросов или вычислить `isAuth`, `canEdit`, `hasPets` | Business-композиция | [Business-композиция](/docs/data/rest/strategies/business-composition) |
|
| Нужно объединить несколько запросов или вычислить `isAuth`, `canEdit`, `hasPets` | Business-композиция | [Business-композиция](/docs/applied/data-fetch/business-composition) |
|
||||||
|
|
||||||
## Правило выбора
|
## Правило выбора
|
||||||
|
|
||||||
@@ -86,8 +86,8 @@ useEffect(() => {
|
|||||||
|
|
||||||
// Плохо — useSWR в компоненте
|
// Плохо — useSWR в компоненте
|
||||||
const { data } = useSWR(
|
const { data } = useSWR(
|
||||||
['pet-store-api', 'pet', 'list', status],
|
['pet-store-api', '/pet/findByStatus?status=available'],
|
||||||
() => petStoreApi.pet.findPetsByStatus({ status }),
|
() => petStoreApi.pet.findPetsByStatus({ status: StatusEnum.Available }),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Плохо — бизнес-флаг внутри GET-хука REST-клиента
|
// Плохо — бизнес-флаг внутри GET-хука REST-клиента
|
||||||
@@ -17,13 +17,19 @@ keywords: [rest, promise.all, параллельные запросы, server co
|
|||||||
## Хорошо
|
## Хорошо
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { petStoreApi } from 'infra/pet-store-api'
|
import { petStoreApi, StatusEnum } from 'infra/pet-store-api'
|
||||||
import { PetsDashboardScreen } from 'screens/pets-dashboard'
|
import { PetsDashboardScreen } from 'screens/pets-dashboard'
|
||||||
|
|
||||||
export default async function PetsDashboardPage() {
|
export default async function PetsDashboardPage() {
|
||||||
const availablePetsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
|
const availablePetsPromise = petStoreApi.pet.findPetsByStatus({
|
||||||
const pendingPetsPromise = petStoreApi.pet.findPetsByStatus({ status: 'pending' })
|
status: StatusEnum.Available,
|
||||||
const soldPetsPromise = petStoreApi.pet.findPetsByStatus({ status: 'sold' })
|
})
|
||||||
|
const pendingPetsPromise = petStoreApi.pet.findPetsByStatus({
|
||||||
|
status: StatusEnum.Pending,
|
||||||
|
})
|
||||||
|
const soldPetsPromise = petStoreApi.pet.findPetsByStatus({
|
||||||
|
status: StatusEnum.Sold,
|
||||||
|
})
|
||||||
|
|
||||||
const [availablePets, pendingPets, soldPets] = await Promise.all([
|
const [availablePets, pendingPets, soldPets] = await Promise.all([
|
||||||
availablePetsPromise,
|
availablePetsPromise,
|
||||||
@@ -45,9 +51,15 @@ export default async function PetsDashboardPage() {
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
export default async function PetsDashboardPage() {
|
export default async function PetsDashboardPage() {
|
||||||
const availablePets = await petStoreApi.pet.findPetsByStatus({ status: 'available' })
|
const availablePets = await petStoreApi.pet.findPetsByStatus({
|
||||||
const pendingPets = await petStoreApi.pet.findPetsByStatus({ status: 'pending' })
|
status: StatusEnum.Available,
|
||||||
const soldPets = await petStoreApi.pet.findPetsByStatus({ status: 'sold' })
|
})
|
||||||
|
const pendingPets = await petStoreApi.pet.findPetsByStatus({
|
||||||
|
status: StatusEnum.Pending,
|
||||||
|
})
|
||||||
|
const soldPets = await petStoreApi.pet.findPetsByStatus({
|
||||||
|
status: StatusEnum.Sold,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PetsDashboardScreen
|
<PetsDashboardScreen
|
||||||
@@ -68,8 +80,8 @@ export default async function PetsDashboardPage() {
|
|||||||
```tsx
|
```tsx
|
||||||
export default async function OrderPage({ params }: OrderPageProps) {
|
export default async function OrderPage({ params }: OrderPageProps) {
|
||||||
const { id } = await params
|
const { id } = await params
|
||||||
const order = await petStoreApi.store.getOrderById(Number(id))
|
const order = await petStoreApi.store.getOrderById({ orderId: Number(id) })
|
||||||
const pet = await petStoreApi.pet.getPetById(order.petId)
|
const pet = await petStoreApi.pet.getPetById({ petId: order.petId })
|
||||||
|
|
||||||
return <OrderScreen order={order} pet={pet} />
|
return <OrderScreen order={order} pet={pet} />
|
||||||
}
|
}
|
||||||
@@ -79,4 +91,4 @@ export default async function OrderPage({ params }: OrderPageProps) {
|
|||||||
|
|
||||||
## Когда выбрать другую стратегию
|
## Когда выбрать другую стратегию
|
||||||
|
|
||||||
Если часть данных не обязательна для первого блока UI, можно запустить промис выше и передать его ниже: [Передача промиса ниже](/docs/data/rest/strategies/pass-promise-down).
|
Если часть данных не обязательна для первого блока UI, можно запустить промис выше и передать его ниже: [Передача промиса ниже](/docs/applied/data-fetch/pass-promise-down).
|
||||||
@@ -19,13 +19,15 @@ keywords: [rest, promise, suspense, streaming, server components]
|
|||||||
```tsx
|
```tsx
|
||||||
// src/app/(routes)/pets/page.tsx
|
// src/app/(routes)/pets/page.tsx
|
||||||
import { Suspense } from 'react'
|
import { Suspense } from 'react'
|
||||||
import { petStoreApi } from 'infra/pet-store-api'
|
import { petStoreApi, StatusEnum } from 'infra/pet-store-api'
|
||||||
import { PetListSection } from 'widgets/pet-list-section'
|
import { PetListSection } from 'widgets/pet-list-section'
|
||||||
import { PetListSkeleton } from 'widgets/pet-list-section'
|
import { PetListSkeleton } from 'widgets/pet-list-section'
|
||||||
import type { Pet } from 'infra/pet-store-api'
|
import type { Pet } from 'infra/pet-store-api'
|
||||||
|
|
||||||
export default function PetsPage() {
|
export default function PetsPage() {
|
||||||
const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
|
const petsPromise = petStoreApi.pet.findPetsByStatus({
|
||||||
|
status: StatusEnum.Available,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
@@ -50,7 +52,7 @@ async function AvailablePets({ petsPromise }: { petsPromise: Promise<Pet[]> }) {
|
|||||||
|
|
||||||
Эта стратегия остаётся серверной. Не используйте её как замену GET-хукам в Client Components.
|
Эта стратегия остаётся серверной. Не используйте её как замену GET-хукам в Client Components.
|
||||||
|
|
||||||
Если данные должны попасть в клиентский SWR-хук, используйте [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data).
|
Если данные должны попасть в клиентский SWR-хук, используйте [Начальные данные для клиентских хуков](/docs/applied/data-fetch/client-hooks-initial-data).
|
||||||
|
|
||||||
## Что не делать
|
## Что не делать
|
||||||
|
|
||||||
@@ -29,12 +29,12 @@ SSR/dynamic rendering нужен, когда данные зависят от т
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// src/app/(routes)/pets/page.tsx
|
// src/app/(routes)/pets/page.tsx
|
||||||
import { petStoreApi } from 'infra/pet-store-api'
|
import { petStoreApi, StatusEnum } from 'infra/pet-store-api'
|
||||||
import { PetsScreen } from 'screens/pets'
|
import { PetsScreen } from 'screens/pets'
|
||||||
|
|
||||||
export default async function PetsPage() {
|
export default async function PetsPage() {
|
||||||
const pets = await petStoreApi.pet.findPetsByStatus({
|
const pets = await petStoreApi.pet.findPetsByStatus({
|
||||||
status: 'available',
|
status: StatusEnum.Available,
|
||||||
})
|
})
|
||||||
|
|
||||||
return <PetsScreen pets={pets} />
|
return <PetsScreen pets={pets} />
|
||||||
@@ -57,7 +57,7 @@ type PetPageProps = {
|
|||||||
|
|
||||||
export default async function PetPage({ params }: PetPageProps) {
|
export default async function PetPage({ params }: PetPageProps) {
|
||||||
const { id } = await params
|
const { id } = await params
|
||||||
const pet = await petStoreApi.pet.getPetById(Number(id)).catch(() => null)
|
const pet = await petStoreApi.pet.getPetById({ petId: Number(id) }).catch(() => null)
|
||||||
|
|
||||||
if (!pet) {
|
if (!pet) {
|
||||||
notFound()
|
notFound()
|
||||||
@@ -73,7 +73,7 @@ export default async function PetPage({ params }: PetPageProps) {
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// Плохо — хуки нельзя вызывать в Server Component
|
// Плохо — хуки нельзя вызывать в Server Component
|
||||||
const { data } = useGetPetList('available')
|
const { data } = useGetPetList({ status: StatusEnum.Available })
|
||||||
|
|
||||||
// Плохо — прямой fetch в обход клиента
|
// Плохо — прямой fetch в обход клиента
|
||||||
const response = await fetch('https://petstore3.swagger.io/api/v3/pet/findByStatus')
|
const response = await fetch('https://petstore3.swagger.io/api/v3/pet/findByStatus')
|
||||||
@@ -83,6 +83,6 @@ const response = await fetch('https://petstore3.swagger.io/api/v3/pet/findByStat
|
|||||||
|
|
||||||
## Когда выбрать другую стратегию
|
## Когда выбрать другую стратегию
|
||||||
|
|
||||||
- Несколько независимых запросов — [Параллельные серверные запросы](/docs/data/rest/strategies/parallel-server-requests).
|
- Несколько независимых запросов — [Параллельные серверные запросы](/docs/applied/data-fetch/parallel-server-requests).
|
||||||
- Часть UI можно грузить отдельно — [Передача промиса ниже](/docs/data/rest/strategies/pass-promise-down).
|
- Часть UI можно грузить отдельно — [Передача промиса ниже](/docs/applied/data-fetch/pass-promise-down).
|
||||||
- Данные нужны клиентскому хуку сразу после гидрации — [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data).
|
- Данные нужны клиентскому хуку сразу после гидрации — [Начальные данные для клиентских хуков](/docs/applied/data-fetch/client-hooks-initial-data).
|
||||||
@@ -134,7 +134,7 @@ export default async function FeedLayout({ children }: FeedLayoutProps) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Подробнее о стратегиях запросов и начальных данных для клиентских хуков: [REST → Стратегии получения данных](/docs/data/rest/strategies/), [REST → Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data).
|
Подробнее о стратегиях запросов и начальных данных для клиентских хуков: [Получение данных](/docs/applied/data-fetch/), [Начальные данные для клиентских хуков](/docs/applied/data-fetch/client-hooks-initial-data).
|
||||||
|
|
||||||
## Инициализация состояния
|
## Инициализация состояния
|
||||||
|
|
||||||
|
|||||||
50
docs/docs/applied/rest-client/index.md
Normal file
50
docs/docs/applied/rest-client/index.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
title: REST-клиент
|
||||||
|
description: Настройка REST-клиента сервиса для работы с внешним API.
|
||||||
|
keywords: [rest, api, данные, infra, клиент, swr, стратегии]
|
||||||
|
---
|
||||||
|
|
||||||
|
# REST-клиент
|
||||||
|
|
||||||
|
Настройка REST-клиента сервиса для работы с внешним API.
|
||||||
|
|
||||||
|
## Настройка
|
||||||
|
|
||||||
|
Для каждого внешнего сервиса создаётся отдельный API-клиент: `pet-store-api`, `billing-api`, `maps-api`.
|
||||||
|
|
||||||
|
На этом этапе внешний API оформляется как модуль слоя `infra/`.
|
||||||
|
|
||||||
|
Клиент отвечает за:
|
||||||
|
|
||||||
|
- генерацию или ручное описание методов API;
|
||||||
|
- настройку `baseUrl`;
|
||||||
|
- заголовки и авторизацию;
|
||||||
|
- обработку ошибок;
|
||||||
|
- кастомизацию и расширение типов;
|
||||||
|
- GET-хуки для клиентских компонентов;
|
||||||
|
- прямое использование методов клиента в серверном коде и submit-функциях;
|
||||||
|
- публичный API модуля.
|
||||||
|
|
||||||
|
Если у API есть OpenAPI-спецификация — клиент генерируется автоматически. Если OpenAPI нет или он неполный — клиент создаётся вручную.
|
||||||
|
|
||||||
|
GET-хуки относятся к клиенту, потому что это прозрачные SWR-обёртки над GET-методами этого клиента.
|
||||||
|
|
||||||
|
Подробнее:
|
||||||
|
|
||||||
|
- [Настройка REST-клиента](/docs/applied/rest-client/setup/)
|
||||||
|
- [Автогенерация из OpenAPI](/docs/applied/rest-client/setup/auto)
|
||||||
|
- [Ручное создание](/docs/applied/rest-client/setup/manual)
|
||||||
|
- [GET-хуки REST-клиента](/docs/applied/rest-client/setup/hooks)
|
||||||
|
- [Использование REST-клиента](/docs/applied/rest-client/usage)
|
||||||
|
|
||||||
|
## Как читать раздел
|
||||||
|
|
||||||
|
Если API ещё не подключён — начните с [Настройки REST-клиента](/docs/applied/rest-client/setup/).
|
||||||
|
|
||||||
|
Если клиент уже создан и нужно вызвать его методы — откройте [Использование REST-клиента](/docs/applied/rest-client/usage).
|
||||||
|
|
||||||
|
Если клиент уже есть, но непонятно как получить данные — начните с раздела [Получение данных](/docs/applied/data-fetch/).
|
||||||
|
|
||||||
|
Если данные нужны в Client Component — сначала проверьте, есть ли [GET-хук REST-клиента](/docs/applied/rest-client/setup/hooks).
|
||||||
|
|
||||||
|
Если в коде появляется бизнес-смысл вроде `isAuth`, `canEdit`, `hasAccess` — это уже не REST-клиент, а `business/`.
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
---
|
---
|
||||||
title: Автогенерация из OpenAPI
|
title: Автогенерация REST-клиента
|
||||||
description: Генерация REST-клиента из OpenAPI-спецификации через @gromlab/api-codegen.
|
description: Генерация REST-клиента из OpenAPI-спецификации.
|
||||||
keywords: [rest, openapi, api-codegen, автогенерация, generated, npx]
|
keywords: [rest, openapi, api-codegen, автогенерация, generated, npx]
|
||||||
---
|
---
|
||||||
|
|
||||||
# Автогенерация из OpenAPI
|
# Автогенерация REST-клиента
|
||||||
|
|
||||||
|
Генерация REST-клиента из OpenAPI-спецификации.
|
||||||
|
|
||||||
|
## Когда использовать
|
||||||
|
|
||||||
Автогенерация используется, когда у API есть актуальная OpenAPI-спецификация. Генератор создаёт TypeScript-клиент, типы и методы API, а разработчик вручную добавляет настройку клиента и GET-хуки.
|
Автогенерация используется, когда у API есть актуальная OpenAPI-спецификация. Генератор создаёт TypeScript-клиент, типы и методы API, а разработчик вручную добавляет настройку клиента и GET-хуки.
|
||||||
|
|
||||||
@@ -66,12 +70,39 @@ src/infra/pet-store-api/generated/
|
|||||||
Для Petstore нужны GET-операции вида:
|
Для Petstore нужны GET-операции вида:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
petStoreApi.pet.findPetsByStatus(...)
|
petStoreApi.pet.findPetsByStatus({ status: StatusEnum.Available })
|
||||||
petStoreApi.pet.getPetById(...)
|
petStoreApi.pet.getPetById({ petId: 10 })
|
||||||
```
|
```
|
||||||
|
|
||||||
Точные сигнатуры зависят от OpenAPI-схемы и версии генератора. В рабочих задачах всегда сверяйтесь с generated-файлом.
|
Точные сигнатуры зависят от OpenAPI-схемы и версии генератора. В рабочих задачах всегда сверяйтесь с generated-файлом.
|
||||||
|
|
||||||
|
## Алгоритм для агента
|
||||||
|
|
||||||
|
После генерации агент должен действовать по шагам:
|
||||||
|
|
||||||
|
1. Открыть `generated/{service-name}.generated.ts`.
|
||||||
|
2. Найти фактические имена GET-методов клиента.
|
||||||
|
3. Для каждого нужного GET-метода найти generated-тип параметров и тип ответа.
|
||||||
|
4. Создать или обновить `client.ts` только для настройки транспорта и экспорта инстанса клиента.
|
||||||
|
5. Создать GET-хуки только для реально нужных GET-методов, не для всех методов API на всякий случай.
|
||||||
|
6. Для каждого GET-хука создать key-функцию формата `[serviceName, endpoint]`.
|
||||||
|
7. В key-функции вернуть `null`, если обязательные параметры не готовы.
|
||||||
|
8. В хуке принять `params?: GeneratedParams | null` и `config?: SWRConfiguration<Data>`.
|
||||||
|
9. В fetcher вызвать generated-метод клиента с `params as GeneratedParams`.
|
||||||
|
10. Экспортировать хук и key-функцию из `hooks/index.ts`.
|
||||||
|
11. Экспортировать наружу только нужные generated-типы, generated enum, DTO и `hooks` через корневой `index.ts`.
|
||||||
|
|
||||||
|
Что агент не должен делать:
|
||||||
|
|
||||||
|
- Не использовать ключ `--swr` генератора.
|
||||||
|
- Не править `generated/*.generated.ts` руками.
|
||||||
|
- Не добавлять GET-хуки для POST, PUT, PATCH, DELETE.
|
||||||
|
- Не добавлять бизнес-флаги, тосты, редиректы и UI-состояние в GET-хук.
|
||||||
|
- Не создавать словари enum-маппинга внутри GET-хука.
|
||||||
|
- Не объявлять DTO и response-типы в файле хука.
|
||||||
|
- Не вызывать `useSWR` условно.
|
||||||
|
- Не добавлять `throw` в fetcher для неготовых params.
|
||||||
|
|
||||||
## `client.ts`
|
## `client.ts`
|
||||||
|
|
||||||
Сгенерированный код не должен напрямую использоваться из приложения. Сначала создаётся настроенный инстанс клиента.
|
Сгенерированный код не должен напрямую использоваться из приложения. Сначала создаётся настроенный инстанс клиента.
|
||||||
@@ -80,8 +111,14 @@ petStoreApi.pet.getPetById(...)
|
|||||||
// src/infra/pet-store-api/client.ts
|
// src/infra/pet-store-api/client.ts
|
||||||
import { Api, HttpClient } from './generated/pet-store-api.generated'
|
import { Api, HttpClient } from './generated/pet-store-api.generated'
|
||||||
|
|
||||||
|
const baseUrl = process.env.NEXT_PUBLIC_PET_STORE_API_BASE_URL
|
||||||
|
|
||||||
|
if (!baseUrl) {
|
||||||
|
throw new Error('NEXT_PUBLIC_PET_STORE_API_BASE_URL is required')
|
||||||
|
}
|
||||||
|
|
||||||
const httpClient = new HttpClient({
|
const httpClient = new HttpClient({
|
||||||
baseUrl: 'https://petstore3.swagger.io/api/v3',
|
baseUrl,
|
||||||
baseApiParams: {
|
baseApiParams: {
|
||||||
secure: false,
|
secure: false,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -93,10 +130,47 @@ const httpClient = new HttpClient({
|
|||||||
export const petStoreApi = new Api(httpClient)
|
export const petStoreApi = new Api(httpClient)
|
||||||
```
|
```
|
||||||
|
|
||||||
В реальном проекте вместо строки `baseUrl` обычно берётся из env-переменной, нормализуется в `client.ts` и передаётся в `HttpClient` через конфиг.
|
Локальное значение `NEXT_PUBLIC_PET_STORE_API_BASE_URL` задаётся в `.env.local`. Не добавляйте fallback вроде `?? 'http://localhost:8080/api/v3'` или `?? ''`: если env-переменная не задана, клиент должен падать с явной ошибкой конфигурации.
|
||||||
|
|
||||||
`client.ts` не содержит расширения типов, `declare module` и `Extended`-типы. Он только настраивает транспорт и экспортирует инстанс клиента.
|
`client.ts` не содержит расширения типов, `declare module` и `Extended`-типы. Он только настраивает транспорт и экспортирует инстанс клиента.
|
||||||
|
|
||||||
|
## GET-хуки
|
||||||
|
|
||||||
|
GET-хуки пишутся вручную после проверки generated-методов.
|
||||||
|
|
||||||
|
Пример для generated-метода `petStoreApi.pet.getPetById({ petId })`:
|
||||||
|
|
||||||
|
```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)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Подробный контракт key-функций, `params`, `config` и запретов описан в разделе [GET-хуки REST-клиента](/docs/applied/rest-client/setup/hooks).
|
||||||
|
|
||||||
## Расширение сгенерированных типов
|
## Расширение сгенерированных типов
|
||||||
|
|
||||||
Сгенерированный файл не правится руками. Если OpenAPI-спецификация неполная или генератор дал слишком общий тип (`object`, `unknown`, отсутствующее поле), расширения живут в `types/`.
|
Сгенерированный файл не правится руками. Если OpenAPI-спецификация неполная или генератор дал слишком общий тип (`object`, `unknown`, отсутствующее поле), расширения живут в `types/`.
|
||||||
@@ -160,7 +234,12 @@ export type { TermRecordItemExtended } from './term'
|
|||||||
```ts
|
```ts
|
||||||
// src/infra/pet-store-api/index.ts
|
// src/infra/pet-store-api/index.ts
|
||||||
export { petStoreApi } from './client'
|
export { petStoreApi } from './client'
|
||||||
export type { Pet } from './generated/pet-store-api.generated'
|
export type {
|
||||||
|
FindPetsByStatusParams,
|
||||||
|
GetPetByIdParams,
|
||||||
|
Pet,
|
||||||
|
} from './generated/pet-store-api.generated'
|
||||||
|
export { PetStatusEnum, StatusEnum } from './generated/pet-store-api.generated'
|
||||||
export * from './hooks'
|
export * from './hooks'
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -190,4 +269,4 @@ npm run codegen:pet-store-api
|
|||||||
|
|
||||||
## Следующий шаг
|
## Следующий шаг
|
||||||
|
|
||||||
После генерации и настройки `client.ts` проверьте серверный вызов метода клиента или добавьте [GET-хук REST-клиента](/docs/data/rest/clients/hooks) для Client Components.
|
После генерации и настройки `client.ts` проверьте [использование REST-клиента](/docs/applied/rest-client/usage) или добавьте [GET-хук REST-клиента](/docs/applied/rest-client/setup/hooks) для Client Components.
|
||||||
313
docs/docs/applied/rest-client/setup/hooks.md
Normal file
313
docs/docs/applied/rest-client/setup/hooks.md
Normal 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-хук](/docs/applied/data-fetch/client-get-hook).
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
---
|
---
|
||||||
title: Создание клиента
|
title: Настройка REST-клиента
|
||||||
description: Из чего состоит REST-клиент и какие части нужно подготовить перед использованием API.
|
description: Подготовка REST-клиента сервиса к использованию.
|
||||||
keywords: [rest, клиент, infra, методы, openapi, get-хуки, swr]
|
keywords: [rest, клиент, infra, методы, openapi, get-хуки, swr]
|
||||||
---
|
---
|
||||||
|
|
||||||
# Создание клиента
|
# Настройка REST-клиента
|
||||||
|
|
||||||
|
Подготовка REST-клиента сервиса к использованию.
|
||||||
|
|
||||||
|
## Что настраиваем
|
||||||
|
|
||||||
REST-клиент — это infra-модуль, через который проект работает с внешним REST API.
|
REST-клиент — это infra-модуль, через который проект работает с внешним REST API.
|
||||||
|
|
||||||
На этом этапе нужно подготовить клиент сервиса: создать оболочку клиента, получить методы API и добавить GET-хуки для клиентских компонентов.
|
На этапе настройки нужно подготовить клиент сервиса: оболочку клиента, методы API и GET-хуки для клиентских компонентов.
|
||||||
|
|
||||||
## Из чего состоит клиент
|
## Из чего состоит клиент
|
||||||
|
|
||||||
@@ -28,6 +32,8 @@ REST-клиент состоит из трёх основных частей:
|
|||||||
|
|
||||||
`client.ts` — только сборочная точка клиента. В нём не размещаются DTO, `declare module`, `Extended`-типы, GET-хуки и бизнес-логика.
|
`client.ts` — только сборочная точка клиента. В нём не размещаются DTO, `declare module`, `Extended`-типы, GET-хуки и бизнес-логика.
|
||||||
|
|
||||||
|
`baseUrl` API задаётся обязательной env-переменной без fallback-значения в коде. Не используйте записи вроде `process.env.NEXT_PUBLIC_PET_STORE_API_BASE_URL ?? 'http://localhost:8080/api/v3'` или `?? ''`: локальный URL должен лежать в `.env.local`, а отсутствие переменной должно приводить к явной ошибке конфигурации.
|
||||||
|
|
||||||
## Методы
|
## Методы
|
||||||
|
|
||||||
Методы описывают конкретные запросы к API.
|
Методы описывают конкретные запросы к API.
|
||||||
@@ -39,8 +45,8 @@ REST-клиент состоит из трёх основных частей:
|
|||||||
|
|
||||||
Подробности:
|
Подробности:
|
||||||
|
|
||||||
- [Автогенерация из OpenAPI](/docs/data/rest/clients/auto)
|
- [Автогенерация из OpenAPI](/docs/applied/rest-client/setup/auto)
|
||||||
- [Ручное создание](/docs/data/rest/clients/manual)
|
- [Ручное создание](/docs/applied/rest-client/setup/manual)
|
||||||
|
|
||||||
## GET-хуки
|
## GET-хуки
|
||||||
|
|
||||||
@@ -50,9 +56,13 @@ REST-клиент состоит из трёх основных частей:
|
|||||||
|
|
||||||
GET-хуки именуются с префиксом `useGet`: `useGetPetList`, `useGetPetDetail`, `useGetCurrentUser`.
|
GET-хуки именуются с префиксом `useGet`: `useGetPetList`, `useGetPetDetail`, `useGetCurrentUser`.
|
||||||
|
|
||||||
|
Каждый GET-хук имеет экспортируемую key-функцию. SWR-ключ всегда имеет формат `[serviceName, endpoint]`: например `['pet-store-api', '/pet/10']`.
|
||||||
|
|
||||||
|
Хук принимает generated-параметры метода и SWR-настройки: `params?: GetPetByIdParams | null`, `config?: SWRConfiguration<Pet>`.
|
||||||
|
|
||||||
Подробности:
|
Подробности:
|
||||||
|
|
||||||
- [GET-хуки REST-клиента](/docs/data/rest/clients/hooks)
|
- [GET-хуки REST-клиента](/docs/applied/rest-client/setup/hooks)
|
||||||
|
|
||||||
## Структура модуля
|
## Структура модуля
|
||||||
|
|
||||||
@@ -61,15 +71,18 @@ src/infra/{service-name}/
|
|||||||
├── client.ts # самописная оболочка и инстанс клиента
|
├── client.ts # самописная оболочка и инстанс клиента
|
||||||
├── generated/ или methods/ # методы API
|
├── generated/ или methods/ # методы API
|
||||||
├── hooks/ # GET-хуки REST-клиента
|
├── hooks/ # GET-хуки REST-клиента
|
||||||
├── types/ # DTO, типы API и расширения типов
|
├── types/ # DTO, именованные response-типы и расширения типов
|
||||||
├── errors/ # ошибки API, если нужны
|
├── errors/ # ошибки API, если нужны
|
||||||
└── index.ts # публичный API
|
└── index.ts # публичный API
|
||||||
```
|
```
|
||||||
|
|
||||||
`index.ts` — единственная точка входа в REST-модуль для внешнего кода.
|
`index.ts` — единственная точка входа в REST-модуль для внешнего кода.
|
||||||
|
|
||||||
|
Если generated-метод возвращает безымянный тип вроде `Record<string, number>`, а этот тип нужен снаружи, вынесите его в `types/`. Не объявляйте DTO внутри `hooks/use-get-*.hook.ts`.
|
||||||
|
|
||||||
## Что делаем дальше
|
## Что делаем дальше
|
||||||
|
|
||||||
1. Создайте методы клиента: [Автогенерация из OpenAPI](/docs/data/rest/clients/auto) или [Ручное создание](/docs/data/rest/clients/manual).
|
1. Создайте методы клиента: [Автогенерация из OpenAPI](/docs/applied/rest-client/setup/auto) или [Ручное создание](/docs/applied/rest-client/setup/manual).
|
||||||
2. Добавьте GET-хуки для GET-запросов: [GET-хуки REST-клиента](/docs/data/rest/clients/hooks).
|
2. Добавьте GET-хуки для GET-запросов: [GET-хуки REST-клиента](/docs/applied/rest-client/setup/hooks).
|
||||||
3. После создания клиента переходите к [Стратегиям получения данных](/docs/data/rest/strategies/).
|
3. Проверьте прямые вызовы клиента: [Использование REST-клиента](/docs/applied/rest-client/usage).
|
||||||
|
4. После настройки клиента переходите к [Получению данных](/docs/applied/data-fetch/).
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
---
|
---
|
||||||
title: Ручное создание
|
title: Ручное создание REST-клиента
|
||||||
description: Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
|
description: Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
|
||||||
keywords: [rest, ручной клиент, fetch, methods, dto, errors, infra]
|
keywords: [rest, ручной клиент, fetch, methods, dto, errors, infra]
|
||||||
---
|
---
|
||||||
|
|
||||||
# Ручное создание
|
# Ручное создание REST-клиента
|
||||||
|
|
||||||
|
Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
|
||||||
|
|
||||||
|
## Когда использовать
|
||||||
|
|
||||||
Ручной REST-клиент используется, когда у API нет OpenAPI-спецификации или она недостаточно точная для автогенерации.
|
Ручной REST-клиент используется, когда у API нет OpenAPI-спецификации или она недостаточно точная для автогенерации.
|
||||||
|
|
||||||
@@ -159,8 +163,14 @@ export function postsMethods(client: PetProjectApiClient) {
|
|||||||
import { PetProjectApiClient } from './client'
|
import { PetProjectApiClient } from './client'
|
||||||
import { postsMethods } from './methods/posts'
|
import { postsMethods } from './methods/posts'
|
||||||
|
|
||||||
|
const baseUrl = process.env.NEXT_PUBLIC_PET_PROJECT_API_BASE_URL
|
||||||
|
|
||||||
|
if (!baseUrl) {
|
||||||
|
throw new Error('NEXT_PUBLIC_PET_PROJECT_API_BASE_URL is required')
|
||||||
|
}
|
||||||
|
|
||||||
const client = new PetProjectApiClient(
|
const client = new PetProjectApiClient(
|
||||||
process.env.NEXT_PUBLIC_API_URL ?? '',
|
baseUrl,
|
||||||
{ 'Content-Type': 'application/json' },
|
{ 'Content-Type': 'application/json' },
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -180,8 +190,9 @@ export * from './hooks'
|
|||||||
- `fetch` используется только внутри базового клиента.
|
- `fetch` используется только внутри базового клиента.
|
||||||
- DTO запросов и ответов живут в `types/`.
|
- DTO запросов и ответов живут в `types/`.
|
||||||
- `client.ts` не содержит DTO, GET-хуки и бизнес-логику.
|
- `client.ts` не содержит DTO, GET-хуки и бизнес-логику.
|
||||||
|
- `baseUrl` берётся из обязательной env-переменной без fallback-значения в коде.
|
||||||
- Методы лежат в `methods/` и возвращают DTO.
|
- Методы лежат в `methods/` и возвращают DTO.
|
||||||
- GET-хуки добавляются отдельно в `hooks/`, если данные нужны в Client Components.
|
- GET-хуки добавляются отдельно в `hooks/`, если данные нужны в Client Components.
|
||||||
- Доменные типы и маппинг DTO живут не в REST-клиенте, а в `business/`.
|
- Доменные типы и маппинг DTO живут не в REST-клиенте, а в `business/`.
|
||||||
|
|
||||||
Следующий шаг: [GET-хуки REST-клиента](/docs/data/rest/clients/hooks) или [Стратегии получения данных](/docs/data/rest/strategies/).
|
Следующий шаг: [Использование REST-клиента](/docs/applied/rest-client/usage), [GET-хуки REST-клиента](/docs/applied/rest-client/setup/hooks) или [Получение данных](/docs/applied/data-fetch/).
|
||||||
21
docs/docs/applied/rest-client/usage.md
Normal file
21
docs/docs/applied/rest-client/usage.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
title: Использование REST-клиента
|
||||||
|
description: Как вызвать готовый REST-клиент в функции.
|
||||||
|
keywords: [rest, api client, submit, generated, pet-store-api]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Использование REST-клиента
|
||||||
|
|
||||||
|
Как вызвать готовый REST-клиент в функции.
|
||||||
|
|
||||||
|
## Пример
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { petStoreApi } from 'infra/pet-store-api'
|
||||||
|
|
||||||
|
export const getPet = async (petId: number) => {
|
||||||
|
const pet = await petStoreApi.pet.getPetById({ petId })
|
||||||
|
|
||||||
|
console.log(pet)
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
---
|
|
||||||
title: Источники данных
|
|
||||||
description: Какие источники данных используются в проекте и как с ними работать.
|
|
||||||
keywords: [данные, api, rest, realtime, клиент, swr, infra, введение, карта раздела]
|
|
||||||
---
|
|
||||||
|
|
||||||
# Источники данных
|
|
||||||
|
|
||||||
Какие источники данных используются в проекте и как с ними работать.
|
|
||||||
|
|
||||||
## Принципы раздела
|
|
||||||
|
|
||||||
- **Клиент — в `infra/`.** Каждый внешний сервис — отдельный модуль слоя `infra/{service-name}/`.
|
|
||||||
- **Прямой `fetch` запрещён.** Запросы идут только через клиент модуля. Исключения — точечные и обоснованные.
|
|
||||||
- **Источник данных диктует канал.** REST, realtime и т.п. — независимые подразделы, у каждого своя модель клиента и своё потребление.
|
|
||||||
- **Серверные и клиентские компоненты потребляют по-разному.** Server Components — прямой `await` метода клиента, клиентские — через готовые GET-хуки REST-клиента (`useGetUserList`, `useGetPostDetail` и т.п.). SWR инкапсулирован в хуке, компонент про него не знает.
|
|
||||||
|
|
||||||
## Карта раздела
|
|
||||||
|
|
||||||
### REST
|
|
||||||
|
|
||||||
Канал «запрос-ответ» по HTTP. Покрывает большинство API.
|
|
||||||
|
|
||||||
- [REST](/docs/data/rest/) — обзор раздела: создание клиента и использование.
|
|
||||||
- **Создание клиента** — как оформляется REST API в проекте:
|
|
||||||
- [Обзор](/docs/data/rest/clients/) — когда нужен клиент и как выбрать подход.
|
|
||||||
- [Автогенерация из OpenAPI](/docs/data/rest/clients/auto) — для API с OpenAPI-спецификацией, через `@gromlab/api-codegen`.
|
|
||||||
- [Ручное создание](/docs/data/rest/clients/manual) — для API без схемы, клиент пишется и поддерживается руками.
|
|
||||||
- [GET-хуки REST-клиента](/docs/data/rest/clients/hooks) — прозрачные SWR-обёртки над GET-методами клиента.
|
|
||||||
- **Использование** — как получать данные через готовый клиент:
|
|
||||||
- [Стратегии получения данных](/docs/data/rest/strategies/) — как выбрать способ получения данных под ситуацию.
|
|
||||||
- [Серверный await](/docs/data/rest/strategies/server-await) — прямой `await` метода клиента в Server Components.
|
|
||||||
- [Параллельные серверные запросы](/docs/data/rest/strategies/parallel-server-requests) — запуск независимых серверных запросов без waterfall.
|
|
||||||
- [Передача промиса ниже](/docs/data/rest/strategies/pass-promise-down) — серверный стриминг через промис и `Suspense`.
|
|
||||||
- [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data) — серверный промис в `SWRConfig fallback`.
|
|
||||||
- [Клиентский GET-хук](/docs/data/rest/strategies/client-get-hook) — получение данных в Client Components через готовый GET-хук.
|
|
||||||
- [Business-композиция](/docs/data/rest/strategies/business-composition) — доменная интерпретация и композиция REST-данных.
|
|
||||||
|
|
||||||
### Realtime
|
|
||||||
|
|
||||||
Канал push-данных: WebSocket, SSE, событийные шины. Транспорт не зашит в правила — важна абстракция «подписка».
|
|
||||||
|
|
||||||
- [Realtime](/docs/data/realtime) — клиент realtime в `infra/`, потребление через `useSWRSubscription` или прямые подписки.
|
|
||||||
|
|
||||||
## Что даёт раздел
|
|
||||||
|
|
||||||
После прочтения раздела понятно:
|
|
||||||
|
|
||||||
- Где живёт код работы с API и почему именно там.
|
|
||||||
- Когда генерировать клиент автоматически, а когда писать вручную, и как структурирован каждый из вариантов.
|
|
||||||
- Какие GET-хуки относятся к REST-клиенту и почему они живут в `infra/{service-name}/hooks/`.
|
|
||||||
- Как выбрать стратегию получения REST-данных под конкретную ситуацию.
|
|
||||||
- Как подключать realtime-источники в общую модель работы с данными.
|
|
||||||
- Какие правила обязательны и какие отклонения допустимы.
|
|
||||||
|
|
||||||
## Что не входит в раздел
|
|
||||||
|
|
||||||
- **Глобальное состояние UI** — Stores, формы, фичефлаги. Это [Stores](/docs/applied/stores).
|
|
||||||
- **Доменная логика** — как данные превращаются в сценарии бизнеса. Это слой `business/` в [Архитектуре](/docs/basics/architecture/).
|
|
||||||
- **Хуки общего назначения** — переиспользуемые хуки UI, не привязанные к конкретному API. Отдельный прикладной раздел для них пока не ведётся.
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
---
|
|
||||||
title: Realtime
|
|
||||||
description: "Работа с push-данными от сервера: подписки и события."
|
|
||||||
keywords: [realtime, websocket, sse, подписка, swr subscription, useSWRSubscription, push, события]
|
|
||||||
---
|
|
||||||
|
|
||||||
# Realtime
|
|
||||||
|
|
||||||
Работа с push-данными от сервера: подписки и события.
|
|
||||||
|
|
||||||
## Принципы
|
|
||||||
|
|
||||||
- **Клиент realtime — в `infra/`** отдельным модулем по имени канала. То же правило, что и для REST: никаких прямых соединений в коде приложения.
|
|
||||||
- **Подписка — единица потребления.** Клиент даёт функцию `subscribe(topic, handler) → unsubscribe`. Внутри — конкретный транспорт.
|
|
||||||
- **Использование на клиенте — два сценария:**
|
|
||||||
- **`useSWRSubscription`** — для данных, которые показываются в UI и должны кешироваться/синхронизироваться с REST.
|
|
||||||
- **Прямая подписка** — для побочных эффектов (тосты, нотификации, аналитика), не привязанных к рендеру.
|
|
||||||
|
|
||||||
## Размещение клиента
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/infra/
|
|
||||||
└── {channel-name}/
|
|
||||||
├── connection.ts # установление соединения, реконнект
|
|
||||||
├── subscribe.ts # subscribe(topic, handler) → unsubscribe
|
|
||||||
├── types.ts
|
|
||||||
└── index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
## Использование через SWR
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
'use client'
|
|
||||||
|
|
||||||
import useSWRSubscription from 'swr/subscription'
|
|
||||||
import { subscribe } from 'infra/notifications'
|
|
||||||
|
|
||||||
export function NotificationCounter() {
|
|
||||||
const { data: count } = useSWRSubscription(
|
|
||||||
['notifications', 'count'],
|
|
||||||
(key, { next }) =>
|
|
||||||
subscribe('notifications.count', (value: number) => next(null, value)),
|
|
||||||
)
|
|
||||||
|
|
||||||
return <span>{count ?? 0}</span>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Плюсы: кеш и дедупликация подписки между несколькими местами рендера; единая модель данных с REST.
|
|
||||||
|
|
||||||
## Прямая подписка
|
|
||||||
|
|
||||||
Для побочных эффектов, которые не влияют на состояние UI напрямую:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
'use client'
|
|
||||||
|
|
||||||
import { useEffect } from 'react'
|
|
||||||
import { subscribe } from 'infra/notifications'
|
|
||||||
import { showToast } from 'ui/toast'
|
|
||||||
|
|
||||||
export function NotificationsToaster() {
|
|
||||||
useEffect(() => {
|
|
||||||
return subscribe('notifications.new', (notification) => {
|
|
||||||
showToast(notification.message)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Возврат `unsubscribe` из `useEffect` обязателен — иначе утечка подписки.
|
|
||||||
|
|
||||||
## Запрет прямых соединений
|
|
||||||
|
|
||||||
Создавать `new WebSocket(...)`, `new EventSource(...)` или подписываться на событийные шины напрямую в коде приложения — запрещено. Все соединения проходят через клиент в `infra/`.
|
|
||||||
|
|
||||||
Исключения — точечные и обоснованные (например, диагностический скрипт), помечаются комментарием.
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
---
|
|
||||||
title: GET-хуки REST-клиента
|
|
||||||
description: Прозрачные SWR-обёртки над GET-методами REST-клиента.
|
|
||||||
keywords: [rest, swr, get-хуки, client components, infra]
|
|
||||||
---
|
|
||||||
|
|
||||||
# GET-хуки REST-клиента
|
|
||||||
|
|
||||||
GET-хуки REST-клиента — прозрачные SWR-обёртки над GET-методами API-клиента. Они нужны, чтобы Client Components получали данные с кешированием, дедупликацией и ревалидацией, не работая с `useSWR` напрямую.
|
|
||||||
|
|
||||||
## Где лежат
|
|
||||||
|
|
||||||
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
|
|
||||||
└── 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/infra/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 'infra/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/infra/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/infra/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/infra/pet-store-api/index.ts
|
|
||||||
export { petStoreApi } from './client'
|
|
||||||
export type { Pet } from './generated/pet-store-api.generated'
|
|
||||||
export * from './hooks'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Где заканчивается infra
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// Хорошо: infra, прозрачный 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 внутри infra-хука
|
|
||||||
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-хук](/docs/data/rest/strategies/client-get-hook).
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
---
|
|
||||||
title: REST
|
|
||||||
description: Как правильно работать с REST API в проекте.
|
|
||||||
keywords: [rest, api, данные, infra, клиент, swr, стратегии]
|
|
||||||
---
|
|
||||||
|
|
||||||
# REST
|
|
||||||
|
|
||||||
Раздел описывает, как правильно работать с REST API в проекте: создать клиент сервиса и выбрать способ получения данных в приложении.
|
|
||||||
|
|
||||||
REST в проекте проходит через два главных этапа:
|
|
||||||
|
|
||||||
1. Создание клиента.
|
|
||||||
2. Использование.
|
|
||||||
|
|
||||||
## 1. Создание клиента
|
|
||||||
|
|
||||||
На этом этапе внешний API оформляется как модуль слоя `infra/`.
|
|
||||||
|
|
||||||
Клиент отвечает за:
|
|
||||||
|
|
||||||
- генерацию или ручное описание методов API;
|
|
||||||
- настройку `baseUrl`;
|
|
||||||
- заголовки и авторизацию;
|
|
||||||
- обработку ошибок;
|
|
||||||
- кастомизацию и расширение типов;
|
|
||||||
- GET-хуки для клиентских компонентов;
|
|
||||||
- публичный API модуля.
|
|
||||||
|
|
||||||
Если у API есть OpenAPI-спецификация — клиент генерируется автоматически. Если OpenAPI нет или он неполный — клиент создаётся вручную.
|
|
||||||
|
|
||||||
GET-хуки относятся к клиенту, потому что это прозрачные SWR-обёртки над GET-методами этого клиента.
|
|
||||||
|
|
||||||
Подробнее:
|
|
||||||
|
|
||||||
- [Создание клиента](/docs/data/rest/clients/)
|
|
||||||
- [Автогенерация из OpenAPI](/docs/data/rest/clients/auto)
|
|
||||||
- [Ручное создание](/docs/data/rest/clients/manual)
|
|
||||||
- [GET-хуки REST-клиента](/docs/data/rest/clients/hooks)
|
|
||||||
|
|
||||||
## 2. Использование
|
|
||||||
|
|
||||||
После создания клиента нужно определить рендер страницы и выбрать, как получать данные в конкретном месте приложения.
|
|
||||||
|
|
||||||
Раздел использования отвечает на вопросы:
|
|
||||||
|
|
||||||
- как понять, можно ли сохранить static/ISR;
|
|
||||||
- когда страница становится dynamic/SSR;
|
|
||||||
- когда получать данные через серверный `await`;
|
|
||||||
- когда запускать несколько серверных запросов параллельно;
|
|
||||||
- когда передавать промис ниже по дереву;
|
|
||||||
- когда передавать начальные данные клиентским GET-хукам;
|
|
||||||
- когда использовать GET-хук в клиентском компоненте;
|
|
||||||
- когда выносить композицию и бизнес-смысл в `business/`.
|
|
||||||
|
|
||||||
Подробнее:
|
|
||||||
|
|
||||||
- [Стратегии получения данных](/docs/data/rest/strategies/)
|
|
||||||
- [Серверный await](/docs/data/rest/strategies/server-await)
|
|
||||||
- [Параллельные серверные запросы](/docs/data/rest/strategies/parallel-server-requests)
|
|
||||||
- [Передача промиса ниже](/docs/data/rest/strategies/pass-promise-down)
|
|
||||||
- [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data)
|
|
||||||
- [Клиентский GET-хук](/docs/data/rest/strategies/client-get-hook)
|
|
||||||
- [Business-композиция](/docs/data/rest/strategies/business-composition)
|
|
||||||
|
|
||||||
## Как читать раздел
|
|
||||||
|
|
||||||
Если API ещё не подключён — начните с [Создания клиента](/docs/data/rest/clients/).
|
|
||||||
|
|
||||||
Если клиент уже есть, но непонятно как получить данные — начните со [Стратегий получения данных](/docs/data/rest/strategies/).
|
|
||||||
|
|
||||||
Если данные нужны в Client Component — сначала проверьте, есть ли [GET-хук REST-клиента](/docs/data/rest/clients/hooks).
|
|
||||||
|
|
||||||
Если в коде появляется бизнес-смысл вроде `isAuth`, `canEdit`, `hasAccess` — это уже не REST-клиент, а `business/`.
|
|
||||||
@@ -40,22 +40,15 @@ description: Стандарты разработки фронтенд-прило
|
|||||||
| Документирование | Как писать JSDoc: что документировать, а что нет? |
|
| Документирование | Как писать JSDoc: что документировать, а что нет? |
|
||||||
| Типизация | Как типизировать: type vs interface, any/unknown? |
|
| Типизация | Как типизировать: type vs interface, any/unknown? |
|
||||||
|
|
||||||
### Создание проекта
|
|
||||||
|
|
||||||
**Как начать новый проект** — варианты установки и эталонный набор инструментов.
|
|
||||||
|
|
||||||
| Раздел | Отвечает на вопрос |
|
|
||||||
|--------|-------------------|
|
|
||||||
| Создание проекта из шаблона | Как начать проект из готового шаблона? |
|
|
||||||
| Создание проекта вручную | Как поднять проект с нуля без шаблона? |
|
|
||||||
| Чистая установка Next.js | Как поставить голый Next.js под дальнейшую сборку? |
|
|
||||||
|
|
||||||
### Настройка
|
### Настройка
|
||||||
|
|
||||||
**Как сконфигурировать проект** — пошаговая настройка инструментов и инфраструктуры.
|
**Как сконфигурировать проект** — пошаговая настройка инструментов и инфраструктуры.
|
||||||
|
|
||||||
| Раздел | Отвечает на вопрос |
|
| Раздел | Отвечает на вопрос |
|
||||||
|--------|-------------------|
|
|--------|-------------------|
|
||||||
|
| Создание проекта из шаблона | Как начать проект из готового шаблона? |
|
||||||
|
| Создание проекта вручную | Как поднять проект с нуля без шаблона? |
|
||||||
|
| Чистая установка Next.js | Как поставить голый Next.js под дальнейшую сборку? |
|
||||||
| Алиасы импортов | Как настроить алиасы импортов? |
|
| Алиасы импортов | Как настроить алиасы импортов? |
|
||||||
| Biome | Как настроить линтер и форматтер? |
|
| Biome | Как настроить линтер и форматтер? |
|
||||||
| PostCSS | Какие плагины PostCSS нужны и как их настроить? |
|
| PostCSS | Какие плагины PostCSS нужны и как их настроить? |
|
||||||
@@ -73,6 +66,8 @@ description: Стандарты разработки фронтенд-прило
|
|||||||
| Структура проекта | Как организованы папки и файлы по SLM? |
|
| Структура проекта | Как организованы папки и файлы по SLM? |
|
||||||
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
|
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
|
||||||
| Файлы роутинга | Как описывать layout, page, loading, error, not-found? |
|
| Файлы роутинга | Как описывать layout, page, loading, error, not-found? |
|
||||||
|
| REST-клиент | Как настроить клиент внешнего REST API? |
|
||||||
|
| Получение данных | Как выбрать способ получения данных под рендер страницы? |
|
||||||
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
|
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
|
||||||
| Стили | Как писать CSS: вложенность, медиа, токены? |
|
| Стили | Как писать CSS: вложенность, медиа, токены? |
|
||||||
| SVG-спрайты | Как использовать SVG-спрайты в коде? |
|
| SVG-спрайты | Как использовать SVG-спрайты в коде? |
|
||||||
@@ -82,16 +77,3 @@ description: Стандарты разработки фронтенд-прило
|
|||||||
| Хуки | _(не заполнен)_ |
|
| Хуки | _(не заполнен)_ |
|
||||||
| Шрифты | _(не заполнен)_ |
|
| Шрифты | _(не заполнен)_ |
|
||||||
| Локализация | _(не заполнен)_ |
|
| Локализация | _(не заполнен)_ |
|
||||||
|
|
||||||
### Данные
|
|
||||||
|
|
||||||
**Как работать с источниками данных** — REST, realtime и потребление в компонентах.
|
|
||||||
|
|
||||||
| Раздел | Отвечает на вопрос |
|
|
||||||
|--------|-------------------|
|
|
||||||
| Источники данных | Как устроена работа с данными в проекте? |
|
|
||||||
| REST: Автоматическая генерация | Как сгенерировать REST-клиент автоматически из OpenAPI? |
|
|
||||||
| REST: Ручное создание | Как написать REST-клиент вручную? |
|
|
||||||
| REST: Серверные компоненты | Как получать данные в серверных компонентах? |
|
|
||||||
| REST: Клиентские компоненты | Как получать данные в клиентских компонентах? |
|
|
||||||
| Realtime | Как работать с realtime-каналами и сокетами? |
|
|
||||||
|
|||||||
Reference in New Issue
Block a user