@vira-ui/react 1.0.2 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,746 +1,411 @@
1
- # @vira-ui/react
2
-
3
- <div align="center">
4
-
5
- **Vira Framework - React hooks for Vira Reactive Protocol (VRP)**
6
-
7
- [![Version](https://img.shields.io/npm/v/@vira-ui/react.svg)](https://www.npmjs.com/package/@vira-ui/react)
8
- [![License](https://img.shields.io/npm/l/@vira-ui/react.svg)](LICENSE)
9
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.3+-blue.svg)](https://www.typescriptlang.org/)
10
-
11
- **React hooks для работы с Vira Reactive Protocol - синхронизация состояния в реальном времени через WebSocket.**
12
-
13
- [Установка](#-установка) • [Быстрый старт](#-быстрый-старт) • [API](#-api-reference) • [Примеры](#-примеры)
14
-
15
- </div>
16
-
17
- ---
18
-
19
- ## 🎯 Что это?
20
-
21
- **@vira-ui/react** это пакет React хуков для работы с **Vira Reactive Protocol (VRP)**. Он предоставляет простой способ синхронизации состояния между клиентом и сервером в реальном времени через WebSocket.
22
-
23
- ### Основные возможности
24
-
25
- - ✅ **useViraState**универсальный хук для работы с VRP каналами
26
- - ✅ **Автоматическая синхронизация**состояние обновляется автоматически при изменениях на сервере
27
- - ✅ **Типизация**полная поддержка TypeScript
28
- - ✅ **Idempotency**поддержка msgId для предотвращения дублирования сообщений
29
- - ✅ **Deep merge** — умное слияние частичных обновлений
30
- - ✅ **Reconnection** — автоматическое переподключение при разрыве связи
31
- - ✅ **Session management** — управление сессиями для восстановления состояния
32
-
33
- ---
34
-
35
- ## 📦 Установка
36
-
37
- ```bash
38
- npm install @vira-ui/react @vira-ui/core react
39
- ```
40
-
41
- **Требования:**
42
- - React 18.2.0+
43
- - @vira-ui/core 1.0.1+
44
- - TypeScript 5.3+ (рекомендуется)
45
-
46
- ---
47
-
48
- ## 🚀 Быстрый старт
49
-
50
- ### 1. Базовое использование
51
-
52
- ```tsx
53
- import { useViraState } from "@vira-ui/react";
54
-
55
- function MyComponent() {
56
- const { data, sendUpdate, sendDiff, sendEvent } = useViraState<User>(
57
- "user:123"
58
- );
59
-
60
- if (!data) {
61
- return <div>Loading...</div>;
62
- }
63
-
64
- return (
65
- <div>
66
- <h1>{data.name}</h1>
67
- <button onClick={() => sendDiff({ name: "New Name" })}>
68
- Обновить имя
69
- </button>
70
- </div>
71
- );
72
- }
73
- ```
74
-
75
- ### 2. С опциями
76
-
77
- ```tsx
78
- import { useViraState } from "@vira-ui/react";
79
-
80
- function MyComponent() {
81
- const { data, sendUpdate, sendDiff, isConnected, error } = useViraState<User>(
82
- "user:123",
83
- {
84
- initial: { name: "Guest", email: "" },
85
- enableMsgId: true,
86
- deepMerge: true,
87
- onOpen: () => console.log("Connected"),
88
- onClose: () => console.log("Disconnected"),
89
- onError: (err) => console.error("Error:", err),
90
- apiUrl: "http://localhost:8080",
91
- authToken: "your-token",
92
- }
93
- );
94
-
95
- if (error) {
96
- return <div>Error: {error.message}</div>;
97
- }
98
-
99
- if (!isConnected) {
100
- return <div>Connecting...</div>;
101
- }
102
-
103
- return <div>{data?.name}</div>;
104
- }
105
- ```
106
-
107
- ### 3. С несколькими каналами
108
-
109
- ```tsx
110
- import { useViraState } from "@vira-ui/react";
111
-
112
- function Dashboard() {
113
- const user = useViraState<User>("user:123");
114
- const notifications = useViraState<Notification[]>("notifications:123");
115
- const tasks = useViraState<Task[]>("tasks:123");
116
-
117
- return (
118
- <div>
119
- <UserProfile data={user.data} />
120
- <NotificationsList data={notifications.data} />
121
- <TasksList data={tasks.data} />
122
- </div>
123
- );
124
- }
125
- ```
126
-
127
- ---
128
-
129
- ## 📚 API Reference
130
-
131
- ### `useViraState<T>(channel, options?)`
132
-
133
- Универсальный хук для работы с VRP каналами.
134
-
135
- **Параметры:**
136
-
137
- - `channel` (string) — имя канала (например, `"user:123"`, `"tasks:456"`)
138
- - `options` (UseViraStateOptions, опционально) — опции хука
139
-
140
- **Возвращает:**
141
-
142
- ```tsx
143
- {
144
- // Текущее состояние данных
145
- data: T | null;
146
-
147
- // Отправить событие на сервер
148
- sendEvent: (name: string, payload: any, msgId?: string) => void;
149
-
150
- // Отправить полное обновление (заменяет состояние)
151
- sendUpdate: (payload: T, msgId?: string) => void;
152
-
153
- // Отправить частичное обновление (сливается с текущим состоянием)
154
- sendDiff: (patch: Partial<T>, msgId?: string) => void;
155
-
156
- // Статус подключения
157
- isConnected: boolean;
158
-
159
- // Ошибка подключения, если есть
160
- error: Error | null;
161
- }
162
- ```
163
-
164
- **Опции:**
165
-
166
- ```tsx
167
- interface UseViraStateOptions<T> {
168
- // Начальное значение состояния
169
- initial?: T | null;
170
-
171
- // Включить поддержку msgId для идемпотентности
172
- enableMsgId?: boolean;
173
-
174
- // Callback при открытии соединения
175
- onOpen?: () => void;
176
-
177
- // Callback при закрытии соединения
178
- onClose?: (event: CloseEvent) => void;
179
-
180
- // Callback при ошибке соединения
181
- onError?: (error: Error) => void;
182
-
183
- // Использовать deep merge для diff патчей (по умолчанию: true)
184
- deepMerge?: boolean;
185
-
186
- // URL API (по умолчанию: VITE_API_URL или 'http://localhost:8080')
187
- apiUrl?: string;
188
-
189
- // Auth token для handshake
190
- authToken?: string;
191
- }
192
- ```
193
-
194
- ---
195
-
196
- ## 🎯 Примеры использования
197
-
198
- ### Пример 1: Простое обновление состояния
199
-
200
- ```tsx
201
- import { useViraState } from "@vira-ui/react";
202
-
203
- interface Counter {
204
- count: number;
205
- }
206
-
207
- function CounterComponent() {
208
- const { data, sendDiff } = useViraState<Counter>("counter:demo");
209
-
210
- const increment = () => {
211
- sendDiff({ count: (data?.count || 0) + 1 });
212
- };
213
-
214
- return (
215
- <div>
216
- <p>Count: {data?.count || 0}</p>
217
- <button onClick={increment}>Increment</button>
218
- </div>
219
- );
220
- }
221
- ```
222
-
223
- ### Пример 2: Работа с пользователем
224
-
225
- ```tsx
226
- import { useViraState } from "@vira-ui/react";
227
-
228
- interface User {
229
- id: string;
230
- name: string;
231
- email: string;
232
- avatar?: string;
233
- }
234
-
235
- function UserProfile({ userId }: { userId: string }) {
236
- const { data, sendDiff, sendEvent, isConnected } = useViraState<User>(
237
- `user:${userId}`,
238
- {
239
- initial: { id: userId, name: "", email: "" },
240
- enableMsgId: true,
241
- }
242
- );
243
-
244
- const updateName = (newName: string) => {
245
- sendDiff({ name: newName });
246
- };
247
-
248
- const updateEmail = (newEmail: string) => {
249
- sendDiff({ email: newEmail });
250
- };
251
-
252
- const uploadAvatar = (avatarUrl: string) => {
253
- sendEvent("user.avatar.upload", { avatarUrl });
254
- };
255
-
256
- if (!isConnected) {
257
- return <div>Connecting...</div>;
258
- }
259
-
260
- return (
261
- <div>
262
- <img src={data?.avatar} alt={data?.name} />
263
- <input
264
- value={data?.name || ""}
265
- onChange={(e) => updateName(e.target.value)}
266
- />
267
- <input
268
- value={data?.email || ""}
269
- onChange={(e) => updateEmail(e.target.value)}
270
- />
271
- </div>
272
- );
273
- }
274
- ```
275
-
276
- ### Пример 3: Kanban доска
277
-
278
- ```tsx
279
- import { useViraState } from "@vira-ui/react";
280
-
281
- interface KanbanBoard {
282
- id: string;
283
- title: string;
284
- columns: Column[];
285
- cards: Record<string, Card>;
286
- }
287
-
288
- function KanbanBoard({ boardId }: { boardId: string }) {
289
- const { data, sendEvent, sendDiff } = useViraState<KanbanBoard>(
290
- `kanban:${boardId}`,
291
- {
292
- enableMsgId: true,
293
- deepMerge: true,
294
- }
295
- );
296
-
297
- const createCard = (columnId: string, title: string) => {
298
- sendEvent("kanban.card.create", {
299
- boardId,
300
- columnId,
301
- title,
302
- at: Date.now(),
303
- });
304
- };
305
-
306
- const moveCard = (
307
- cardId: string,
308
- fromColumnId: string,
309
- toColumnId: string,
310
- newOrder: number
311
- ) => {
312
- sendEvent("kanban.card.move", {
313
- boardId,
314
- cardId,
315
- fromColumnId,
316
- toColumnId,
317
- newOrder,
318
- at: Date.now(),
319
- });
320
- };
321
-
322
- const updateCard = (cardId: string, updates: Partial<Card>) => {
323
- sendDiff({
324
- cards: {
325
- [cardId]: updates,
326
- },
327
- });
328
- };
329
-
330
- if (!data) {
331
- return <div>Loading board...</div>;
332
- }
333
-
334
- return (
335
- <div>
336
- <h1>{data.title}</h1>
337
- {data.columns.map((column) => (
338
- <Column
339
- key={column.id}
340
- column={column}
341
- cards={getColumnCards(data, column.id)}
342
- onCreateCard={(title) => createCard(column.id, title)}
343
- onMoveCard={moveCard}
344
- onUpdateCard={updateCard}
345
- />
346
- ))}
347
- </div>
348
- );
349
- }
350
- ```
351
-
352
- ### Пример 4: Чат приложение
353
-
354
- ```tsx
355
- import { useViraState } from "@vira-ui/react";
356
-
357
- interface Chat {
358
- messages: Message[];
359
- participants: User[];
360
- }
361
-
362
- function ChatRoom({ roomId }: { roomId: string }) {
363
- const { data, sendEvent, sendDiff } = useViraState<Chat>(`chat:${roomId}`, {
364
- initial: { messages: [], participants: [] },
365
- enableMsgId: true,
366
- });
367
-
368
- const sendMessage = (text: string) => {
369
- sendEvent("chat.message.send", {
370
- roomId,
371
- text,
372
- timestamp: Date.now(),
373
- });
374
- };
375
-
376
- const addParticipant = (user: User) => {
377
- sendDiff({
378
- participants: [...(data?.participants || []), user],
379
- });
380
- };
381
-
382
- return (
383
- <div>
384
- <div>
385
- {data?.messages.map((msg) => (
386
- <MessageBubble key={msg.id} message={msg} />
387
- ))}
388
- </div>
389
- <MessageInput onSend={sendMessage} />
390
- </div>
391
- );
392
- }
393
- ```
394
-
395
- ### Пример 5: Редактирование документа
396
-
397
- ```tsx
398
- import { useViraState } from "@vira-ui/react";
399
-
400
- interface Document {
401
- id: string;
402
- title: string;
403
- content: string;
404
- version: number;
405
- updatedAt: number;
406
- }
407
-
408
- function DocumentEditor({ docId }: { docId: string }) {
409
- const { data, sendDiff, isConnected } = useViraState<Document>(
410
- `document:${docId}`,
411
- {
412
- enableMsgId: true,
413
- deepMerge: true,
414
- onOpen: () => console.log("Document loaded"),
415
- }
416
- );
417
-
418
- const updateTitle = (title: string) => {
419
- sendDiff({
420
- title,
421
- updatedAt: Date.now(),
422
- });
423
- };
424
-
425
- const updateContent = (content: string) => {
426
- sendDiff({
427
- content,
428
- updatedAt: Date.now(),
429
- });
430
- };
431
-
432
- if (!isConnected || !data) {
433
- return <div>Loading document...</div>;
434
- }
435
-
436
- return (
437
- <div>
438
- <input
439
- value={data.title}
440
- onChange={(e) => updateTitle(e.target.value)}
441
- />
442
- <textarea
443
- value={data.content}
444
- onChange={(e) => updateContent(e.target.value)}
445
- />
446
- <div>Version: {data.version}</div>
447
- </div>
448
- );
449
- }
450
- ```
451
-
452
- ### Пример 6: С обработкой ошибок
453
-
454
- ```tsx
455
- import { useViraState } from "@vira-ui/react";
456
-
457
- function RobustComponent() {
458
- const { data, sendDiff, isConnected, error } = useViraState<MyData>(
459
- "my:channel",
460
- {
461
- onError: (err) => {
462
- console.error("Connection error:", err);
463
- // Можно отправить в систему мониторинга
464
- // sendToMonitoring(err);
465
- },
466
- onClose: (event) => {
467
- console.log("Connection closed:", event.code, event.reason);
468
- // Можно показать уведомление пользователю
469
- },
470
- }
471
- );
472
-
473
- if (error) {
474
- return (
475
- <div>
476
- <p>Ошибка подключения: {error.message}</p>
477
- <button onClick={() => window.location.reload()}>
478
- Перезагрузить
479
- </button>
480
- </div>
481
- );
482
- }
483
-
484
- if (!isConnected) {
485
- return <div>Подключение...</div>;
486
- }
487
-
488
- return <div>{/* Ваш контент */}</div>;
489
- }
490
- ```
491
-
492
- ---
493
-
494
- ## 🔧 Конфигурация
495
-
496
- ### Переменные окружения
497
-
498
- ```env
499
- # URL API сервера
500
- VITE_API_URL=http://localhost:8080
501
-
502
- # Auth token для подключения
503
- VITE_AUTH_TOKEN=your-secret-token
504
- ```
505
-
506
- ### Программная конфигурация
507
-
508
- ```tsx
509
- import { useViraState } from "@vira-ui/react";
510
-
511
- function MyComponent() {
512
- const { data } = useViraState("my:channel", {
513
- apiUrl: "https://api.example.com",
514
- authToken: "your-token",
515
- });
516
- }
517
- ```
518
-
519
- ---
520
-
521
- ## 🎯 Vira Reactive Protocol (VRP)
522
-
523
- ### Типы сообщений
524
-
525
- VRP поддерживает следующие типы сообщений:
526
-
527
- #### 1. `update` — полное обновление состояния
528
-
529
- ```tsx
530
- sendUpdate({ name: "John", age: 30 });
531
- ```
532
-
533
- #### 2. `diff` — частичное обновление
534
-
535
- ```tsx
536
- sendDiff({ name: "Jane" }); // Обновит только name
537
- ```
538
-
539
- #### 3. `event` — событие
540
-
541
- ```tsx
542
- sendEvent("user.created", { userId: "123" });
543
- ```
544
-
545
- ### Каналы
546
-
547
- Каналы используются для изоляции данных:
548
-
549
- ```tsx
550
- // Пользователь
551
- "user:123"
552
-
553
- // Задачи
554
- "tasks:456"
555
-
556
- // Уведомления
557
- "notifications:123"
558
-
559
- // Демо канал
560
- "demo"
561
- ```
562
-
563
- ### Idempotency (msgId)
564
-
565
- Для предотвращения дублирования сообщений используйте `enableMsgId`:
566
-
567
- ```tsx
568
- const { sendUpdate } = useViraState("my:channel", {
569
- enableMsgId: true, // Автоматически генерирует msgId
570
- });
571
-
572
- // Или вручную
573
- sendUpdate(data, "custom-msg-id");
574
- ```
575
-
576
- ### Deep Merge
577
-
578
- По умолчанию используется deep merge для diff обновлений:
579
-
580
- ```tsx
581
- // Текущее состояние
582
- { user: { name: "John", age: 30 } }
583
-
584
- // Отправляем diff
585
- sendDiff({ user: { name: "Jane" } });
586
-
587
- // Результат (deep merge)
588
- { user: { name: "Jane", age: 30 } }
589
- ```
590
-
591
- Для shallow merge:
592
-
593
- ```tsx
594
- useViraState("my:channel", {
595
- deepMerge: false, // Shallow merge
596
- });
597
- ```
598
-
599
- ---
600
-
601
- ## 🔥 Best Practices
602
-
603
- ### 1. Используйте типизацию
604
-
605
- ```tsx
606
- // ✅ Хорошо
607
- interface User {
608
- id: string;
609
- name: string;
610
- }
611
-
612
- const { data } = useViraState<User>("user:123");
613
-
614
- // ❌ Плохо
615
- const { data } = useViraState("user:123");
616
- ```
617
-
618
- ### 2. Используйте enableMsgId для критичных операций
619
-
620
- ```tsx
621
- // ✅ Хорошо
622
- useViraState("payment:123", {
623
- enableMsgId: true, // Предотвращает дублирование платежей
624
- });
625
-
626
- // ❌ Плохо
627
- useViraState("payment:123"); // Может привести к дублированию
628
- ```
629
-
630
- ### 3. Обрабатывайте ошибки
631
-
632
- ```tsx
633
- // ✅ Хорошо
634
- const { data, error, isConnected } = useViraState("my:channel", {
635
- onError: (err) => console.error(err),
636
- });
637
-
638
- if (error) {
639
- return <ErrorComponent error={error} />;
640
- }
641
-
642
- // ❌ Плохо
643
- const { data } = useViraState("my:channel");
644
- // Нет обработки ошибок
645
- ```
646
-
647
- ### 4. Используйте initial для лучшего UX
648
-
649
- ```tsx
650
- // ✅ Хорошо
651
- useViraState("user:123", {
652
- initial: { name: "Loading...", email: "" },
653
- });
654
-
655
- // ❌ Плохо
656
- useViraState("user:123");
657
- // Пользователь видит пустой экран
658
- ```
659
-
660
- ### 5. Используйте sendDiff для частичных обновлений
661
-
662
- ```tsx
663
- // ✅ Хорошо
664
- sendDiff({ name: "New Name" }); // Обновляет только name
665
-
666
- // ❌ Плохо
667
- sendUpdate({ ...data, name: "New Name" }); // Отправляет весь объект
668
- ```
669
-
670
- ---
671
-
672
- ## ❓ FAQ
673
-
674
- **Q: В чём разница между `sendUpdate` и `sendDiff`?**
675
-
676
- A: `sendUpdate` заменяет всё состояние целиком, а `sendDiff` сливает изменения с текущим состоянием. Используйте `sendDiff` для частичных обновлений.
677
-
678
- **Q: Что такое msgId и зачем он нужен?**
679
-
680
- A: `msgId` — это уникальный идентификатор сообщения для предотвращения дублирования. Включите `enableMsgId: true` для критичных операций.
681
-
682
- **Q: Как работает deep merge?**
683
-
684
- A: Deep merge рекурсивно сливает объекты, сохраняя вложенные структуры. Например, `{ user: { name: "Jane" } }` обновит только `name`, не затрагивая другие поля `user`.
685
-
686
- **Q: Можно ли использовать несколько каналов одновременно?**
687
-
688
- A: Да! Просто вызовите `useViraState` несколько раз с разными каналами.
689
-
690
- **Q: Что происходит при разрыве соединения?**
691
-
692
- A: Клиент автоматически пытается переподключиться. Вы можете отслеживать статус через `isConnected` и обрабатывать через `onClose` callback.
693
-
694
- **Q: Как работает сессия?**
695
-
696
- A: Сессия сохраняется между переподключениями, что позволяет восстановить подписки и состояние.
697
-
698
- **Q: Можно ли использовать без TypeScript?**
699
-
700
- A: Да, но TypeScript рекомендуется для лучшего DX и безопасности типов.
701
-
702
- ---
703
-
704
- ## 🛣️ Roadmap
705
-
706
- ### v1.1 (В разработке)
707
- - [ ] Поддержка batch операций
708
- - [ ] Оптимистичные обновления
709
- - [ ] Offline queue
710
- - [ ] Compression для больших payload
711
-
712
- ### v1.2 (Планируется)
713
- - [ ] DevTools интеграция
714
- - [ ] Метрики и мониторинг
715
- - [ ] Поддержка подписок на несколько каналов
716
- - [ ] WebRTC fallback
717
-
718
- ---
719
-
720
- ## 📄 License
721
-
722
- MIT
723
-
724
- ---
725
-
726
- ## 🤝 Contributing
727
-
728
- Мы приветствуем вклад! Пожалуйста, прочитайте [CONTRIBUTING.md](../../CONTRIBUTING.md) для деталей.
729
-
730
- ---
731
-
732
- ## 📞 Support
733
-
734
- - **GitHub Issues**: [Создать issue](https://github.com/skrolikov/vira-core/issues)
735
- - **Discussions**: [Обсуждения](https://github.com/skrolikov/vira-core/discussions)
736
-
737
- ---
738
-
739
- <div align="center">
740
-
741
- **Сделано с ❤️ командой Vira**
742
-
743
- [GitHub](https://github.com/skrolikov/vira-core) • [Документация](https://vira.dev/react) • [Примеры](https://vira.dev/examples)
744
-
745
- </div>
746
-
1
+ # @vira-ui/react
2
+
3
+ <div align="center">
4
+
5
+ **React хуки для Vira Reactive Protocol (VRP)**
6
+
7
+ [![Version](https://img.shields.io/npm/v/@vira-ui/react.svg)](https://www.npmjs.com/package/@vira-ui/react)
8
+ [![License](https://img.shields.io/npm/l/@vira-ui/react.svg)](LICENSE)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.3+-blue.svg)](https://www.typescriptlang.org/)
10
+
11
+ **Синхронизация состояния между клиентом и сервером через WebSocket**
12
+
13
+ [Установка](#-установка) • [Быстрый старт](#-быстрый-старт) • [Документация](#-документация)
14
+
15
+ </div>
16
+
17
+ ---
18
+
19
+ ## 🎯 Что это?
20
+
21
+ **@vira-ui/react** предоставляет React хуки для работы с Vira Reactive Protocol (VRP) протоколом для real-time синхронизации состояния между клиентом и сервером через WebSocket.
22
+
23
+ ### Основные возможности
24
+
25
+ - ✅ **Автоматическая синхронизация** состояние обновляется при изменениях на сервере
26
+ - ✅ **Двусторонняя связь**можно отправлять события и обновления на сервер
27
+ - ✅ **Diff-патчи**обновляются только изменённые части данных
28
+ - ✅ **Переподключение**автоматическое восстановление соединения
29
+ - ✅ **TypeScript** — полная типизация
30
+
31
+ ---
32
+
33
+ ## 📦 Установка
34
+
35
+ ```bash
36
+ npm install @vira-ui/react @vira-ui/core react
37
+ ```
38
+
39
+ **Требования:**
40
+ - React 18.2.0+
41
+ - `@vira-ui/core` ^1.0.0
42
+ - Сервер с поддержкой Vira Reactive Protocol
43
+
44
+ ---
45
+
46
+ ## 🚀 Быстрый старт
47
+
48
+ ### useViraState
49
+
50
+ Основной хук для синхронизации состояния:
51
+
52
+ ```tsx
53
+ import { useViraState } from '@vira-ui/react';
54
+
55
+ interface User {
56
+ id: string;
57
+ name: string;
58
+ email: string;
59
+ }
60
+
61
+ function UserProfile({ userId }: { userId: string }) {
62
+ const { data, sendUpdate, sendDiff, isConnected } = useViraState<User>(
63
+ `user:${userId}`,
64
+ {
65
+ initial: { id: userId, name: 'Guest', email: '' },
66
+ onOpen: () => console.log('Connected'),
67
+ deepMerge: true
68
+ }
69
+ );
70
+
71
+ if (!isConnected) return <div>Connecting...</div>;
72
+ if (!data) return <div>Loading...</div>;
73
+
74
+ return (
75
+ <div>
76
+ <h1>{data.name}</h1>
77
+ <p>{data.email}</p>
78
+ <button onClick={() => sendDiff({ name: 'New Name' })}>
79
+ Update Name
80
+ </button>
81
+ </div>
82
+ );
83
+ }
84
+ ```
85
+
86
+ ---
87
+
88
+ ## 📚 Документация
89
+
90
+ ### useViraState
91
+
92
+ Подключается к VRP каналу и синхронизирует состояние.
93
+
94
+ **Параметры:**
95
+ - `channel` — имя канала (например: `"user:123"`, `"tasks"`)
96
+ - `options` — опции конфигурации
97
+
98
+ **Возвращает:**
99
+ - `data` — текущее состояние
100
+ - `sendUpdate(payload)` — полная замена состояния
101
+ - `sendDiff(patch)` — частичное обновление (merge)
102
+ - `sendEvent(name, payload)` — отправка события
103
+ - `isConnected` — статус соединения
104
+ - `isLoading` — статус загрузки
105
+
106
+ **Опции:**
107
+ - `initial` начальное значение
108
+ - `apiUrl` — URL сервера (по умолчанию из `VITE_API_URL`)
109
+ - `authToken` — токен авторизации
110
+ - `deepMerge` глубокое слияние для diff-патчей
111
+ - `enableMsgId` — поддержка idempotency
112
+ - `onOpen`, `onClose`, `onError` — колбэки событий соединения
113
+
114
+ ### Примеры использования
115
+
116
+ #### Список элементов
117
+
118
+ ```tsx
119
+ import { useViraState } from '@vira-ui/react';
120
+
121
+ interface Task {
122
+ id: string;
123
+ title: string;
124
+ completed: boolean;
125
+ }
126
+
127
+ function TasksList() {
128
+ const { data, sendEvent, sendDiff } = useViraState<Task[]>(
129
+ 'tasks',
130
+ { initial: [] }
131
+ );
132
+
133
+ const toggleTask = (taskId: string) => {
134
+ const task = data?.find(t => t.id === taskId);
135
+ if (task) {
136
+ sendDiff({ [taskId]: { completed: !task.completed } });
137
+ }
138
+ };
139
+
140
+ const createTask = (title: string) => {
141
+ sendEvent('task.created', {
142
+ id: crypto.randomUUID(),
143
+ title,
144
+ completed: false
145
+ });
146
+ };
147
+
148
+ return (
149
+ <div>
150
+ {data?.map(task => (
151
+ <div key={task.id}>
152
+ <input
153
+ type="checkbox"
154
+ checked={task.completed}
155
+ onChange={() => toggleTask(task.id)}
156
+ />
157
+ <span>{task.title}</span>
158
+ </div>
159
+ ))}
160
+ <button onClick={() => createTask('New Task')}>
161
+ Add Task
162
+ </button>
163
+ </div>
164
+ );
165
+ }
166
+ ```
167
+
168
+ #### Одиночный элемент
169
+
170
+ ```tsx
171
+ function UserDetails({ userId }: { userId: string }) {
172
+ const { data, sendDiff, isConnected } = useViraState<User>(
173
+ `user:${userId}`,
174
+ {
175
+ initial: null,
176
+ deepMerge: true
177
+ }
178
+ );
179
+
180
+ const updateName = (name: string) => {
181
+ sendDiff({ name });
182
+ };
183
+
184
+ if (!isConnected) {
185
+ return <div>Connecting to server...</div>;
186
+ }
187
+
188
+ if (!data) {
189
+ return <div>Loading user...</div>;
190
+ }
191
+
192
+ return (
193
+ <div>
194
+ <input
195
+ value={data.name}
196
+ onChange={(e) => updateName(e.target.value)}
197
+ />
198
+ <p>Email: {data.email}</p>
199
+ </div>
200
+ );
201
+ }
202
+ ```
203
+
204
+ #### С обработкой ошибок
205
+
206
+ ```tsx
207
+ function DataComponent({ channel }: { channel: string }) {
208
+ const { data, sendEvent, isConnected, error } = useViraState(
209
+ channel,
210
+ {
211
+ initial: null,
212
+ onError: (err) => {
213
+ console.error('VRP Error:', err);
214
+ // Можно показать уведомление пользователю
215
+ },
216
+ onClose: () => {
217
+ console.log('Connection closed, reconnecting...');
218
+ }
219
+ }
220
+ );
221
+
222
+ if (error) {
223
+ return <div>Error: {error.message}</div>;
224
+ }
225
+
226
+ if (!isConnected) {
227
+ return <div>Reconnecting...</div>;
228
+ }
229
+
230
+ return <div>{/* ... */}</div>;
231
+ }
232
+ ```
233
+
234
+ #### С авторизацией
235
+
236
+ ```tsx
237
+ function AuthenticatedComponent() {
238
+ const authToken = useAuthToken(); // Ваш хук для получения токена
239
+
240
+ const { data } = useViraState('protected:data', {
241
+ authToken,
242
+ onError: (err) => {
243
+ if (err.message.includes('unauthorized')) {
244
+ // Перенаправить на страницу входа
245
+ }
246
+ }
247
+ });
248
+
249
+ return <div>{/* ... */}</div>;
250
+ }
251
+ ```
252
+
253
+ ---
254
+
255
+ ## 🔄 Паттерны использования
256
+
257
+ ### Real-time обновления
258
+
259
+ ```tsx
260
+ function LiveDashboard() {
261
+ const { data } = useViraState('dashboard:stats', {
262
+ initial: { users: 0, orders: 0 }
263
+ });
264
+
265
+ // Данные автоматически обновляются при изменениях на сервере
266
+ return (
267
+ <div>
268
+ <div>Users: {data?.users}</div>
269
+ <div>Orders: {data?.orders}</div>
270
+ </div>
271
+ );
272
+ }
273
+ ```
274
+
275
+ ### Оптимистичные обновления
276
+
277
+ ```tsx
278
+ function OptimisticUpdate() {
279
+ const { data, sendDiff } = useViraState('user:123', {
280
+ initial: { name: 'John' }
281
+ });
282
+
283
+ const updateName = (newName: string) => {
284
+ // Сразу обновляем локально (оптимистично)
285
+ sendDiff({ name: newName });
286
+
287
+ // Сервер подтвердит или откатит изменение
288
+ };
289
+
290
+ return (
291
+ <input
292
+ value={data?.name}
293
+ onChange={(e) => updateName(e.target.value)}
294
+ />
295
+ );
296
+ }
297
+ ```
298
+
299
+ ### События вместо обновлений
300
+
301
+ ```tsx
302
+ function EventDriven() {
303
+ const { sendEvent } = useViraState('tasks', { initial: [] });
304
+
305
+ const handleComplete = (taskId: string) => {
306
+ // Отправляем событие вместо прямого обновления
307
+ sendEvent('task.completed', { taskId });
308
+
309
+ // Сервер обработает событие и обновит состояние
310
+ };
311
+
312
+ return <button onClick={() => handleComplete('123')}>Complete</button>;
313
+ }
314
+ ```
315
+
316
+ ---
317
+
318
+ ## 🔗 Интеграция
319
+
320
+ Обычно используется вместе с:
321
+
322
+ - **@vira-ui/core** базовый фреймворк
323
+ - **@vira-ui/bindings-react** — компоненты с автоматическим связыванием
324
+
325
+ ---
326
+
327
+ ## 📖 Примеры
328
+
329
+ ### Kanban доска
330
+
331
+ ```tsx
332
+ function KanbanBoard() {
333
+ const { data, sendEvent } = useViraState<Column[]>('kanban:board', {
334
+ initial: []
335
+ });
336
+
337
+ const moveCard = (cardId: string, fromColumn: string, toColumn: string) => {
338
+ sendEvent('card.moved', {
339
+ cardId,
340
+ fromColumn,
341
+ toColumn
342
+ });
343
+ };
344
+
345
+ return (
346
+ <div className="kanban-board">
347
+ {data?.map(column => (
348
+ <Column
349
+ key={column.id}
350
+ column={column}
351
+ onMoveCard={moveCard}
352
+ />
353
+ ))}
354
+ </div>
355
+ );
356
+ }
357
+ ```
358
+
359
+ ### Чат
360
+
361
+ ```tsx
362
+ function ChatRoom({ roomId }: { roomId: string }) {
363
+ const { data, sendEvent } = useViraState<Message[]>(
364
+ `chat:${roomId}`,
365
+ { initial: [] }
366
+ );
367
+
368
+ const sendMessage = (text: string) => {
369
+ sendEvent('message.sent', {
370
+ id: crypto.randomUUID(),
371
+ text,
372
+ timestamp: Date.now()
373
+ });
374
+ };
375
+
376
+ return (
377
+ <div>
378
+ <div className="messages">
379
+ {data?.map(msg => (
380
+ <Message key={msg.id} message={msg} />
381
+ ))}
382
+ </div>
383
+ <MessageInput onSend={sendMessage} />
384
+ </div>
385
+ );
386
+ }
387
+ ```
388
+
389
+ ---
390
+
391
+ ## 🔥 Best Practices
392
+
393
+ 1. **Используйте типизацию** — всегда указывайте тип для `useViraState<T>`
394
+ 2. **Обрабатывайте ошибки** — используйте `onError` для обработки ошибок соединения
395
+ 3. **Оптимистичные обновления** используйте `sendDiff` для мгновенного обновления UI
396
+ 4. **События для действий** — используйте `sendEvent` для действий, которые должны обрабатываться на сервере
397
+
398
+ ---
399
+
400
+ ## 📄 License
401
+
402
+ MIT
403
+
404
+ ---
405
+
406
+ ## 🔗 Связанные пакеты
407
+
408
+ - [`@vira-ui/core`](../core/README.md) - Базовый фреймворк с VRP клиентом
409
+ - [`@vira-ui/bindings-react`](../bindings-react/README.md) - Компоненты с auto-binding
410
+ - [`@vira-ui/ui`](../ui/README.md) - UI компоненты
411
+