80 lines
3.3 KiB
Markdown
80 lines
3.3 KiB
Markdown
|
|
---
|
|||
|
|
title: Realtime
|
|||
|
|
description: "Работа с push-данными от сервера: подписки и события."
|
|||
|
|
keywords: [realtime, websocket, sse, подписка, swr subscription, useSWRSubscription, push, события]
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Realtime
|
|||
|
|
|
|||
|
|
Работа с push-данными от сервера: подписки и события.
|
|||
|
|
|
|||
|
|
## Принципы
|
|||
|
|
|
|||
|
|
- **Клиент realtime — в `infrastructure/`** отдельным модулем по имени канала. То же правило, что и для REST: никаких прямых соединений в коде приложения.
|
|||
|
|
- **Подписка — единица потребления.** Клиент даёт функцию `subscribe(topic, handler) → unsubscribe`. Внутри — конкретный транспорт.
|
|||
|
|
- **Использование на клиенте — два сценария:**
|
|||
|
|
- **`useSWRSubscription`** — для данных, которые показываются в UI и должны кешироваться/синхронизироваться с REST.
|
|||
|
|
- **Прямая подписка** — для побочных эффектов (тосты, нотификации, аналитика), не привязанных к рендеру.
|
|||
|
|
|
|||
|
|
## Размещение клиента
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
src/infrastructure/
|
|||
|
|
└── {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 'infrastructure/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 'infrastructure/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(...)` или подписываться на событийные шины напрямую в коде приложения — запрещено. Все соединения проходят через клиент в `infrastructure/`.
|
|||
|
|
|
|||
|
|
Исключения — точечные и обоснованные (например, диагностический скрипт), помечаются комментарием.
|