@fozy-labs/rx-toolkit 0.5.3-rc.1 → 0.5.3

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.
Files changed (207) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +143 -137
  3. package/dist/common/devtools/combineDevtools.js +3 -3
  4. package/dist/common/devtools/index.d.ts +3 -3
  5. package/dist/common/devtools/index.js +3 -3
  6. package/dist/common/devtools/reduxDevtools.d.ts +1 -1
  7. package/dist/common/devtools/reduxDevtools.js +17 -17
  8. package/dist/common/devtools/types.d.ts +0 -6
  9. package/dist/common/options/DefaultOptions.d.ts +1 -1
  10. package/dist/common/options/SharedOptions.d.ts +3 -2
  11. package/dist/common/options/SharedOptions.js +6 -0
  12. package/dist/common/options/index.d.ts +1 -1
  13. package/dist/common/options/index.js +1 -1
  14. package/dist/common/react/index.d.ts +2 -2
  15. package/dist/common/react/index.js +2 -2
  16. package/dist/common/react/useConstant.js +1 -1
  17. package/dist/common/utils/deepEqual.js +1 -1
  18. package/dist/common/utils/index.d.ts +3 -3
  19. package/dist/common/utils/index.js +3 -3
  20. package/dist/common/utils/shallowEqual.js +1 -1
  21. package/dist/index.d.ts +8 -7
  22. package/dist/index.js +8 -7
  23. package/dist/query/SKIP_TOKEN.js +1 -1
  24. package/dist/query/api/createCommand.d.ts +21 -0
  25. package/dist/query/api/createCommand.js +20 -0
  26. package/dist/query/api/createOperation.d.ts +5 -3
  27. package/dist/query/api/createOperation.js +6 -2
  28. package/dist/query/api/createResource.d.ts +2 -2
  29. package/dist/query/api/createResourceDuplicator.d.ts +2 -2
  30. package/dist/query/core/Command/Command.d.ts +35 -0
  31. package/dist/query/core/{Opertation/Operation.js → Command/Command.js} +13 -14
  32. package/dist/query/core/Command/CommandAgent.d.ts +19 -0
  33. package/dist/query/core/{Opertation/OperationAgent.js → Command/CommandAgent.js} +13 -13
  34. package/dist/query/core/Command/index.d.ts +2 -0
  35. package/dist/query/core/Command/index.js +2 -0
  36. package/dist/query/core/Operation/Operation.d.ts +8 -0
  37. package/dist/query/core/Operation/Operation.js +4 -0
  38. package/dist/query/core/Operation/OperationAgent.d.ts +4 -0
  39. package/dist/query/core/Operation/OperationAgent.js +4 -0
  40. package/dist/query/core/QueriesCache.d.ts +2 -2
  41. package/dist/query/core/QueriesCache.js +1 -1
  42. package/dist/query/core/QueriesLifetimeHooks.d.ts +1 -1
  43. package/dist/query/core/QueriesLifetimeHooks.js +7 -7
  44. package/dist/query/core/Resource/Resource.d.ts +16 -16
  45. package/dist/query/core/Resource/Resource.js +7 -7
  46. package/dist/query/core/Resource/ResourceAgent.d.ts +2 -2
  47. package/dist/query/core/Resource/ResourceAgent.js +3 -3
  48. package/dist/query/core/Resource/ResourceDuplicator.d.ts +17 -17
  49. package/dist/query/core/Resource/ResourceDuplicator.js +18 -20
  50. package/dist/query/core/Resource/ResourceDuplicatorAgent.d.ts +6 -6
  51. package/dist/query/core/Resource/ResourceDuplicatorAgent.js +3 -3
  52. package/dist/query/core/Resource/ResourceRef.d.ts +2 -2
  53. package/dist/query/core/Resource/ResourceRef.js +12 -12
  54. package/dist/query/index.d.ts +11 -8
  55. package/dist/query/index.js +14 -8
  56. package/dist/query/lib/IndirectMap.js +4 -4
  57. package/dist/query/lib/ReactiveCache.d.ts +1 -1
  58. package/dist/query/react/useCommandAgent.d.ts +24 -0
  59. package/dist/query/react/useCommandAgent.js +39 -0
  60. package/dist/query/react/useOperationAgent.d.ts +6 -8
  61. package/dist/query/react/useOperationAgent.js +6 -23
  62. package/dist/query/react/useResourceAgent.d.ts +4 -4
  63. package/dist/query/react/useResourceAgent.js +1 -1
  64. package/dist/query/react/useResourceRef.d.ts +3 -3
  65. package/dist/query/react/useResourceRef.js +7 -2
  66. package/dist/query/types/Command.types.d.ts +154 -0
  67. package/dist/query/types/Command.types.js +1 -0
  68. package/dist/query/types/Operation.types.d.ts +13 -154
  69. package/dist/query/types/Resource.types.d.ts +7 -5
  70. package/dist/query/types/index.d.ts +4 -3
  71. package/dist/query/types/index.js +5 -3
  72. package/dist/query-v2/api/createApi.d.ts +10 -0
  73. package/dist/query-v2/api/createApi.js +83 -0
  74. package/dist/query-v2/core/common/CacheEntry.d.ts +29 -0
  75. package/dist/query-v2/core/common/CacheEntry.js +71 -0
  76. package/dist/query-v2/core/common/CacheMap.d.ts +38 -0
  77. package/dist/query-v2/core/common/CacheMap.js +127 -0
  78. package/dist/query-v2/core/common/LifecycleHooks.d.ts +22 -0
  79. package/dist/query-v2/core/common/LifecycleHooks.js +104 -0
  80. package/dist/query-v2/core/common/index.d.ts +3 -0
  81. package/dist/query-v2/core/common/index.js +3 -0
  82. package/dist/query-v2/core/index.d.ts +3 -0
  83. package/dist/query-v2/core/index.js +3 -0
  84. package/dist/query-v2/core/machines/Machine.d.ts +14 -0
  85. package/dist/query-v2/core/machines/Machine.js +33 -0
  86. package/dist/query-v2/core/machines/MachineError.d.ts +11 -0
  87. package/dist/query-v2/core/machines/MachineError.js +26 -0
  88. package/dist/query-v2/core/machines/MachineIdle.d.ts +8 -0
  89. package/dist/query-v2/core/machines/MachineIdle.js +19 -0
  90. package/dist/query-v2/core/machines/MachinePending.d.ts +12 -0
  91. package/dist/query-v2/core/machines/MachinePending.js +29 -0
  92. package/dist/query-v2/core/machines/MachineRefreshing.d.ts +14 -0
  93. package/dist/query-v2/core/machines/MachineRefreshing.js +46 -0
  94. package/dist/query-v2/core/machines/MachineSuccess.d.ts +16 -0
  95. package/dist/query-v2/core/machines/MachineSuccess.js +42 -0
  96. package/dist/query-v2/core/machines/MachineWithData.d.ts +18 -0
  97. package/dist/query-v2/core/machines/MachineWithData.js +40 -0
  98. package/dist/query-v2/core/machines/Patcher.d.ts +20 -0
  99. package/dist/query-v2/core/machines/Patcher.js +104 -0
  100. package/dist/query-v2/core/machines/index.d.ts +8 -0
  101. package/dist/query-v2/core/machines/index.js +8 -0
  102. package/dist/query-v2/core/resource/ResourceV2.d.ts +120 -0
  103. package/dist/query-v2/core/resource/ResourceV2.js +464 -0
  104. package/dist/query-v2/core/resource/ResourceV2Agent.d.ts +26 -0
  105. package/dist/query-v2/core/resource/ResourceV2Agent.js +132 -0
  106. package/dist/query-v2/core/resource/index.d.ts +2 -0
  107. package/dist/query-v2/core/resource/index.js +2 -0
  108. package/dist/query-v2/index.d.ts +11 -0
  109. package/dist/query-v2/index.js +17 -0
  110. package/dist/query-v2/lib/NO_VALUE.d.ts +2 -0
  111. package/dist/query-v2/lib/NO_VALUE.js +1 -0
  112. package/dist/query-v2/lib/SKIP_TOKEN.d.ts +2 -0
  113. package/dist/query-v2/lib/SKIP_TOKEN.js +1 -0
  114. package/dist/query-v2/lib/index.d.ts +4 -0
  115. package/dist/query-v2/lib/index.js +3 -0
  116. package/dist/query-v2/lib/stableStringify.d.ts +8 -0
  117. package/dist/query-v2/lib/stableStringify.js +23 -0
  118. package/dist/query-v2/plugins/ReactHooksPlugin.d.ts +25 -0
  119. package/dist/query-v2/plugins/ReactHooksPlugin.js +19 -0
  120. package/dist/query-v2/plugins/types.d.ts +1 -0
  121. package/dist/query-v2/plugins/types.js +1 -0
  122. package/dist/query-v2/react/__tests__/helpers.d.ts +12 -0
  123. package/dist/query-v2/react/__tests__/helpers.js +33 -0
  124. package/dist/query-v2/react/index.d.ts +2 -0
  125. package/dist/query-v2/react/index.js +2 -0
  126. package/dist/query-v2/react/useResourceV2Agent.d.ts +12 -0
  127. package/dist/query-v2/react/useResourceV2Agent.js +36 -0
  128. package/dist/query-v2/react/useResourceV2Ref.d.ts +12 -0
  129. package/dist/query-v2/react/useResourceV2Ref.js +57 -0
  130. package/dist/query-v2/snapshot/Snapshot.d.ts +13 -0
  131. package/dist/query-v2/snapshot/Snapshot.js +76 -0
  132. package/dist/query-v2/types/agent.types.d.ts +54 -0
  133. package/dist/query-v2/types/agent.types.js +1 -0
  134. package/dist/query-v2/types/api.types.d.ts +22 -0
  135. package/dist/query-v2/types/api.types.js +1 -0
  136. package/dist/query-v2/types/cache.types.d.ts +37 -0
  137. package/dist/query-v2/types/cache.types.js +1 -0
  138. package/dist/query-v2/types/index.d.ts +9 -0
  139. package/dist/query-v2/types/index.js +9 -0
  140. package/dist/query-v2/types/lifecycle.types.d.ts +25 -0
  141. package/dist/query-v2/types/lifecycle.types.js +1 -0
  142. package/dist/query-v2/types/machine.types.d.ts +67 -0
  143. package/dist/query-v2/types/machine.types.js +1 -0
  144. package/dist/query-v2/types/plugin.types.d.ts +38 -0
  145. package/dist/query-v2/types/plugin.types.js +1 -0
  146. package/dist/query-v2/types/resource.types.d.ts +35 -0
  147. package/dist/query-v2/types/resource.types.js +1 -0
  148. package/dist/query-v2/types/shared.types.d.ts +20 -0
  149. package/dist/query-v2/types/shared.types.js +1 -0
  150. package/dist/query-v2/types/snapshot.types.d.ts +21 -0
  151. package/dist/query-v2/types/snapshot.types.js +1 -0
  152. package/dist/signals/base/Batcher.js +9 -5
  153. package/dist/signals/base/ComputeCache.js +3 -3
  154. package/dist/signals/base/DependencyTracker.js +1 -1
  155. package/dist/signals/base/Devtools.d.ts +3 -2
  156. package/dist/signals/base/Devtools.js +54 -27
  157. package/dist/signals/base/Indexer.js +1 -1
  158. package/dist/signals/base/ReadonlySignal.js +1 -1
  159. package/dist/signals/base/SyncObservable.d.ts +1 -2
  160. package/dist/signals/base/SyncObservable.js +2 -5
  161. package/dist/signals/base/index.d.ts +6 -6
  162. package/dist/signals/base/index.js +6 -6
  163. package/dist/signals/index.d.ts +5 -4
  164. package/dist/signals/index.js +5 -4
  165. package/dist/signals/operators/index.d.ts +1 -1
  166. package/dist/signals/operators/index.js +1 -1
  167. package/dist/signals/operators/signalize.d.ts +1 -1
  168. package/dist/signals/react/index.d.ts +1 -1
  169. package/dist/signals/react/index.js +1 -1
  170. package/dist/signals/signals/Computed.d.ts +3 -4
  171. package/dist/signals/signals/Computed.js +18 -10
  172. package/dist/signals/signals/Effect.js +2 -1
  173. package/dist/signals/signals/LocalState.d.ts +44 -0
  174. package/dist/signals/signals/{LocalSignal.js → LocalState.js} +62 -28
  175. package/dist/signals/signals/Signal.d.ts +8 -7
  176. package/dist/signals/signals/Signal.js +4 -1
  177. package/dist/signals/signals/State.d.ts +4 -5
  178. package/dist/signals/signals/State.js +23 -9
  179. package/dist/signals/signals/index.d.ts +5 -5
  180. package/dist/signals/signals/index.js +5 -6
  181. package/dist/signals/types/SignalOptions.d.ts +16 -0
  182. package/dist/signals/types/SignalOptions.js +1 -0
  183. package/dist/signals/types/index.d.ts +3 -1
  184. package/dist/signals/types/index.js +3 -1
  185. package/dist/signals/types/normalizeSignalOptions.d.ts +2 -0
  186. package/dist/signals/types/normalizeSignalOptions.js +10 -0
  187. package/dist/signals/types/signals.types.d.ts +6 -2
  188. package/docs/CHANGELOG.md +111 -32
  189. package/docs/CONTRIBUTING.md +230 -0
  190. package/docs/contributing/ai-assisted-development.md +47 -0
  191. package/docs/contributing/query-v2/README.md +379 -0
  192. package/docs/{release → contributing/release}/README.md +59 -59
  193. package/docs/devtools/README.md +228 -228
  194. package/docs/migrations/0.5.0.md +58 -58
  195. package/docs/migrations/query-v2.md +171 -0
  196. package/docs/options/README.md +92 -90
  197. package/docs/query/README.md +575 -571
  198. package/docs/query-v2/README.md +280 -0
  199. package/docs/query-v2/api-reference.md +235 -0
  200. package/docs/query-v2/optimistic-updates.md +148 -0
  201. package/docs/query-v2/ssr.md +130 -0
  202. package/docs/signals/README.md +300 -295
  203. package/docs/usage/react/README.md +309 -307
  204. package/package.json +86 -63
  205. package/dist/query/core/Opertation/Operation.d.ts +0 -35
  206. package/dist/query/core/Opertation/OperationAgent.d.ts +0 -19
  207. package/dist/signals/signals/LocalSignal.d.ts +0 -32
@@ -1,307 +1,309 @@
1
- # React интеграция
2
-
3
- RxToolkit предоставляет набор React хуков для эффективной интеграции с реактивными системами библиотеки. Все хуки оптимизированы для минимальных ре-рендеров и максимальной производительности.
4
-
5
- ## Основные хуки
6
-
7
- ### useSignal
8
-
9
- Подписывается на изменения сигнала и возвращает текущее значение.
10
-
11
- ```tsx
12
- import { Signal, Computed, useSignal } from '@fozy-labs/rx-toolkit';
13
-
14
- const counter$ = Signal.create(0);
15
- const doubled$ = Signal.compute(() => counter$() * 2);
16
-
17
- function Counter() {
18
- const count = useSignal(counter$);
19
- const doubled = useSignal(doubled$);
20
-
21
- return (
22
- <div>
23
- <p>Count: {count}</p>
24
- <p>Doubled: {doubled}</p>
25
- <button onClick={() => counter$.set(counter$.peek() + 1)}>
26
- Increment
27
- </button>
28
- </div>
29
- );
30
- }
31
- ```
32
-
33
- **Особенности:**
34
- - Автоматическая подписка и отписка при размонтировании
35
- - Не вызывает ре-рендер, если значение не изменилось
36
-
37
- ---
38
-
39
- ## RxQuery хуки
40
-
41
- ### useResourceAgent
42
-
43
- Подписывается на состояние ресурса и автоматически инициирует запрос при монтировании или изменении аргументов.
44
-
45
- ```tsx
46
- import { useResourceAgent, SKIP } from '@fozy-labs/rx-toolkit';
47
- import { userResource } from '../api/userResource';
48
-
49
- function UserProfile({ userId }: { userId: string | null }) {
50
- const userQuery = useResourceAgent(
51
- userResource,
52
- userId ? { id: userId } : SKIP
53
- );
54
-
55
- if (userQuery.isInitialLoading) {
56
- return <div>Загрузка...</div>;
57
- }
58
-
59
- if (userQuery.isError) {
60
- return <div>Ошибка: {String(userQuery.error)}</div>;
61
- }
62
-
63
- if (userQuery.isReloading) {
64
- // Показываем данные + индикатор перезагрузки
65
- }
66
-
67
- return (
68
- <div>
69
- <h1>{userQuery.data?.name}</h1>
70
- <p>{userQuery.data?.email}</p>
71
- </div>
72
- );
73
- }
74
- ```
75
-
76
- **Возвращаемое значение (ResourceQueryState):**
77
-
78
- | Поле | Тип | Описание |
79
- |--------------------|-------------|---------------------------------------|
80
- | `isLoading` | `boolean` | Любая загрузка (первая или повторная) |
81
- | `isInitialLoading` | `boolean` | Первая загрузка (данных еще нет) |
82
- | `isReloading` | `boolean` | Перезагрузка (данные уже есть) |
83
- | `isDone` | `boolean` | Завершен ли запрос |
84
- | `isSuccess` | `boolean` | Успешно ли завершен последний запрос |
85
- | `isError` | `boolean` | Произошла ли ошибка |
86
- | `isLocked` | `boolean` | Заблокирован ли ресурс для операцией |
87
- | `error` | `unknown` | Объект ошибки |
88
- | `data` | `D["Data"]` | Данные ресурса |
89
- | `args` | `D["Args"]` | Аргументы последнего запроса |
90
-
91
- **Особенности:**
92
- - Автоматическая подписка на состояние ресурса
93
- - Умная инициация: не повторяет запрос для тех же аргументов
94
- - Поддержка `SKIP` токена для условного пропуска запроса
95
- - При смене аргументов показывает предыдущие данные во время загрузки новых
96
-
97
- ### useOperationAgent
98
-
99
- Создает агент операции и возвращает кортеж `[trigger, state]`.
100
-
101
- ```tsx
102
- import { useOperationAgent } from '@fozy-labs/rx-toolkit';
103
- import { updateUserOperation } from '../api/updateUserOperation';
104
-
105
- function EditUserForm({ user }: { user: User }) {
106
- const [updateUser, updateState] = useOperationAgent(updateUserOperation);
107
-
108
- const handleSubmit = async (event: React.FormEvent) => {
109
- event.preventDefault();
110
- const formData = new FormData(event.target as HTMLFormElement);
111
-
112
- try {
113
- const result = await updateUser({
114
- id: user.id,
115
- data: Object.fromEntries(formData)
116
- });
117
- console.log('Обновлено:', result);
118
- } catch (error) {
119
- console.error('Ошибка:', error);
120
- }
121
- };
122
-
123
- return (
124
- <form onSubmit={handleSubmit}>
125
- <input name="name" defaultValue={user.name} />
126
- <input name="email" defaultValue={user.email} />
127
-
128
- <button type="submit" disabled={updateState.isLoading}>
129
- {updateState.isLoading ? 'Сохранение...' : 'Сохранить'}
130
- </button>
131
-
132
- {updateState.isError && (
133
- <p className="error">Ошибка: {String(updateState.error)}</p>
134
- )}
135
- </form>
136
- );
137
- }
138
- ```
139
-
140
- **Возвращаемое значение:**
141
- ```typescript
142
- [
143
- trigger: (args: Args) => Promise<Data>, // Функция запуска операции
144
- state: OperationQueryState // Текущее состояние
145
- ]
146
- ```
147
-
148
- **trigger функция:**
149
- - Возвращает Promise с результатом операции
150
- - При ошибке Promise реджектится
151
- - Функция стабильна (не меняется между рендерами)
152
-
153
- **state объект:**
154
-
155
- | Поле | Тип | Описание |
156
- |---------------|-------------|---------------------------|
157
- | `isInitiated` | `boolean` | Была ли операция запущена |
158
- | `isLoading` | `boolean` | Выполняется ли операция |
159
- | `isDone` | `boolean` | Завершена ли операция |
160
- | `isSuccess` | `boolean` | Успешно ли завершена |
161
- | `isError` | `boolean` | Произошла ли ошибка |
162
- | `error` | `unknown` | Объект ошибки |
163
- | `data` | `D["Data"]` | Результат операции |
164
-
165
- ### useResourceRef
166
-
167
- Возвращает ссылку на элемент кэша ресурса для низкоуровневых операций.
168
-
169
- ```tsx
170
- import { useResourceRef, useResourceAgent } from '@fozy-labs/rx-toolkit';
171
- import { todoResource } from '../api/todoResource';
172
-
173
- function TodoItem({ todo }: { todo: Todo }) {
174
- const todoQuery = useResourceAgent(todoResource, undefined);
175
- const todoRef = useResourceRef(todoResource, undefined);
176
-
177
- const [pendingTransaction, setPendingTransaction] = useState(null);
178
-
179
- const handleToggle = () => {
180
- // Создаем транзакцию для изменения
181
- const transaction = todoRef.patch((draft) => {
182
- const item = draft.items.find(i => i.id === todo.id);
183
- if (item) item.completed = !item.completed;
184
- });
185
-
186
- if (transaction) {
187
- setPendingTransaction(transaction);
188
- }
189
- };
190
-
191
- const handleSave = async () => {
192
- try {
193
- await saveToServer(todo.id, !todo.completed);
194
- pendingTransaction?.commit();
195
- } catch {
196
- pendingTransaction?.abort(); // Откатить изменения
197
- }
198
- setPendingTransaction(null);
199
- };
200
-
201
- return (
202
- <div>
203
- <input
204
- type="checkbox"
205
- checked={todo.completed}
206
- onChange={handleToggle}
207
- />
208
- {pendingTransaction && (
209
- <>
210
- <button onClick={handleSave}>Сохранить</button>
211
- <button onClick={() => {
212
- pendingTransaction.abort();
213
- setPendingTransaction(null);
214
- }}>
215
- Отмена
216
- </button>
217
- </>
218
- )}
219
- </div>
220
- );
221
- }
222
- ```
223
-
224
- **Возвращаемое значение (ResourceRefInstanse):**
225
-
226
- | Метод | Описание |
227
- |-------|----------|
228
- | `has` | Проверка наличия элемента в кэше |
229
- | `lock()` | Блокировка ресурса, возвращает `{ unlock }` |
230
- | `unlockOne()` | Снятие одной блокировки |
231
- | `patch(fn)` | Создание patch-транзакции |
232
- | `invalidate()` | Инвалидация кэша |
233
- | `create(data)` | Создание элемента в кэше |
234
-
235
-
236
- ---
237
-
238
- ## Паттерны использования
239
-
240
- ### Store класс
241
-
242
- ```tsx
243
- import { Signal, useSignal } from '@fozy-labs/rx-toolkit';
244
-
245
- class CounterStore {
246
- count$ = Signal.create(0, 'counter');
247
- doubled$ = Signal.compute(() => this.count$() * 2);
248
-
249
- increment = () => this.count$.set(this.count$() + 1);
250
- decrement = () => this.count$.set(this.count$() - 1);
251
- reset = () => this.count$.set(0);
252
- }
253
-
254
- // Singleton
255
- const counterStore = new CounterStore();
256
-
257
- function Counter() {
258
- const count = useSignal(counterStore.count$);
259
- const doubled = useSignal(counterStore.doubled$);
260
-
261
- return (
262
- <div>
263
- <p>{count} × 2 = {doubled}</p>
264
- <button onClick={counterStore.increment}>+</button>
265
- <button onClick={counterStore.decrement}>-</button>
266
- <button onClick={counterStore.reset}>Reset</button>
267
- </div>
268
- );
269
- }
270
- ```
271
-
272
- ### Условные запросы
273
-
274
- ```tsx
275
- function UserStats({ userId, showStats }) {
276
- const statsQuery = useResourceAgent(
277
- statsResource,
278
- showStats && userId ? { userId } : SKIP
279
- );
280
-
281
- if (!showStats) return null;
282
-
283
- return <StatsDisplay data={statsQuery.data} />;
284
- }
285
- ```
286
-
287
- ### Комбинирование ресурсов
288
-
289
- ```tsx
290
- function Dashboard() {
291
- const userQuery = useResourceAgent(userResource, { id: currentUserId });
292
- const settingsQuery = useResourceAgent(settingsResource, undefined);
293
-
294
- const isLoading = userQuery.isLoading || settingsQuery.isLoading;
295
- const isError = userQuery.isError || settingsQuery.isError;
296
-
297
- if (isLoading) return <Loader />;
298
- if (isError) return <Error />;
299
-
300
- return (
301
- <div>
302
- <UserInfo user={userQuery.data} />
303
- <Settings settings={settingsQuery.data} />
304
- </div>
305
- );
306
- }
307
- ```
1
+ # React интеграция
2
+
3
+ RxToolkit предоставляет набор React хуков для эффективной интеграции с реактивными системами библиотеки. Все хуки оптимизированы для минимальных ре-рендеров и максимальной производительности.
4
+
5
+ ## Основные хуки
6
+
7
+ ### useSignal
8
+
9
+ Подписывается на изменения сигнала и возвращает текущее значение.
10
+
11
+ ```tsx
12
+ import { Signal, Computed, useSignal } from '@fozy-labs/rx-toolkit';
13
+
14
+ const counter$ = Signal.state(0);
15
+ const doubled$ = Signal.compute(() => counter$() * 2);
16
+
17
+ function Counter() {
18
+ const count = useSignal(counter$);
19
+ const doubled = useSignal(doubled$);
20
+
21
+ return (
22
+ <div>
23
+ <p>Count: {count}</p>
24
+ <p>Doubled: {doubled}</p>
25
+ <button onClick={() => counter$.set(counter$.peek() + 1)}>
26
+ Increment
27
+ </button>
28
+ </div>
29
+ );
30
+ }
31
+ ```
32
+
33
+ **Особенности:**
34
+ - Автоматическая подписка и отписка при размонтировании
35
+ - Не вызывает ре-рендер, если значение не изменилось
36
+
37
+ ---
38
+
39
+ ## RxQuery хуки
40
+
41
+ ### useResourceAgent
42
+
43
+ Подписывается на состояние ресурса и автоматически инициирует запрос при монтировании или изменении аргументов.
44
+
45
+ ```tsx
46
+ import { useResourceAgent, SKIP } from '@fozy-labs/rx-toolkit';
47
+ import { userResource } from '../api/userResource';
48
+
49
+ function UserProfile({ userId }: { userId: string | null }) {
50
+ const userQuery = useResourceAgent(
51
+ userResource,
52
+ userId ? { id: userId } : SKIP
53
+ );
54
+
55
+ if (userQuery.isInitialLoading) {
56
+ return <div>Загрузка...</div>;
57
+ }
58
+
59
+ if (userQuery.isError) {
60
+ return <div>Ошибка: {String(userQuery.error)}</div>;
61
+ }
62
+
63
+ if (userQuery.isReloading) {
64
+ // Показываем данные + индикатор перезагрузки
65
+ }
66
+
67
+ return (
68
+ <div>
69
+ <h1>{userQuery.data?.name}</h1>
70
+ <p>{userQuery.data?.email}</p>
71
+ </div>
72
+ );
73
+ }
74
+ ```
75
+
76
+ **Возвращаемое значение (ResourceQueryState):**
77
+
78
+ | Поле | Тип | Описание |
79
+ |--------------------|-------------|---------------------------------------|
80
+ | `isLoading` | `boolean` | Любая загрузка (первая или повторная) |
81
+ | `isInitialLoading` | `boolean` | Первая загрузка (данных еще нет) |
82
+ | `isReloading` | `boolean` | Перезагрузка (данные уже есть) |
83
+ | `isDone` | `boolean` | Завершен ли запрос |
84
+ | `isSuccess` | `boolean` | Успешно ли завершен последний запрос |
85
+ | `isError` | `boolean` | Произошла ли ошибка |
86
+ | `isLocked` | `boolean` | Заблокирован ли ресурс командой |
87
+ | `error` | `unknown` | Объект ошибки |
88
+ | `data` | `D["Data"]` | Данные ресурса |
89
+ | `args` | `D["Args"]` | Аргументы последнего запроса |
90
+
91
+ **Особенности:**
92
+ - Автоматическая подписка на состояние ресурса
93
+ - Умная инициация: не повторяет запрос для тех же аргументов
94
+ - Поддержка `SKIP` токена для условного пропуска запроса
95
+ - При смене аргументов показывает предыдущие данные во время загрузки новых
96
+
97
+ ### useCommandAgent
98
+
99
+ Создает агент команды и возвращает кортеж `[trigger, state]`.
100
+
101
+ > **Note:** `useOperationAgent` является deprecated-алиасом для `useCommandAgent` и будет удалён в v0.6.0.
102
+
103
+ ```tsx
104
+ import { useCommandAgent } from '@fozy-labs/rx-toolkit';
105
+ import { updateUserCommand } from '../api/updateUserCommand';
106
+
107
+ function EditUserForm({ user }: { user: User }) {
108
+ const [updateUser, updateState] = useCommandAgent(updateUserCommand);
109
+
110
+ const handleSubmit = async (event: React.FormEvent) => {
111
+ event.preventDefault();
112
+ const formData = new FormData(event.target as HTMLFormElement);
113
+
114
+ try {
115
+ const result = await updateUser({
116
+ id: user.id,
117
+ data: Object.fromEntries(formData)
118
+ });
119
+ console.log('Обновлено:', result);
120
+ } catch (error) {
121
+ console.error('Ошибка:', error);
122
+ }
123
+ };
124
+
125
+ return (
126
+ <form onSubmit={handleSubmit}>
127
+ <input name="name" defaultValue={user.name} />
128
+ <input name="email" defaultValue={user.email} />
129
+
130
+ <button type="submit" disabled={updateState.isLoading}>
131
+ {updateState.isLoading ? 'Сохранение...' : 'Сохранить'}
132
+ </button>
133
+
134
+ {updateState.isError && (
135
+ <p className="error">Ошибка: {String(updateState.error)}</p>
136
+ )}
137
+ </form>
138
+ );
139
+ }
140
+ ```
141
+
142
+ **Возвращаемое значение:**
143
+ ```typescript
144
+ [
145
+ trigger: (args: Args) => Promise<Data>, // Функция запуска команды
146
+ state: CommandQueryState // Текущее состояние
147
+ ]
148
+ ```
149
+
150
+ **trigger функция:**
151
+ - Возвращает Promise с результатом команды
152
+ - При ошибке Promise реджектится
153
+ - Функция стабильна (не меняется между рендерами)
154
+
155
+ **state объект:**
156
+
157
+ | Поле | Тип | Описание |
158
+ |---------------|-------------|---------------------------|
159
+ | `isInitiated` | `boolean` | Была ли команда запущена |
160
+ | `isLoading` | `boolean` | Выполняется ли команда |
161
+ | `isDone` | `boolean` | Завершена ли команда |
162
+ | `isSuccess` | `boolean` | Успешно ли завершена |
163
+ | `isError` | `boolean` | Произошла ли ошибка |
164
+ | `error` | `unknown` | Объект ошибки |
165
+ | `data` | `D["Data"]` | Результат команды |
166
+
167
+ ### useResourceRef
168
+
169
+ Возвращает ссылку на элемент кэша ресурса для низкоуровневых операций.
170
+
171
+ ```tsx
172
+ import { useResourceRef, useResourceAgent } from '@fozy-labs/rx-toolkit';
173
+ import { todoResource } from '../api/todoResource';
174
+
175
+ function TodoItem({ todo }: { todo: Todo }) {
176
+ const todoQuery = useResourceAgent(todoResource, undefined);
177
+ const todoRef = useResourceRef(todoResource, undefined);
178
+
179
+ const [pendingTransaction, setPendingTransaction] = useState(null);
180
+
181
+ const handleToggle = () => {
182
+ // Создаем транзакцию для изменения
183
+ const transaction = todoRef.patch((draft) => {
184
+ const item = draft.items.find(i => i.id === todo.id);
185
+ if (item) item.completed = !item.completed;
186
+ });
187
+
188
+ if (transaction) {
189
+ setPendingTransaction(transaction);
190
+ }
191
+ };
192
+
193
+ const handleSave = async () => {
194
+ try {
195
+ await saveToServer(todo.id, !todo.completed);
196
+ pendingTransaction?.commit();
197
+ } catch {
198
+ pendingTransaction?.abort(); // Откатить изменения
199
+ }
200
+ setPendingTransaction(null);
201
+ };
202
+
203
+ return (
204
+ <div>
205
+ <input
206
+ type="checkbox"
207
+ checked={todo.completed}
208
+ onChange={handleToggle}
209
+ />
210
+ {pendingTransaction && (
211
+ <>
212
+ <button onClick={handleSave}>Сохранить</button>
213
+ <button onClick={() => {
214
+ pendingTransaction.abort();
215
+ setPendingTransaction(null);
216
+ }}>
217
+ Отмена
218
+ </button>
219
+ </>
220
+ )}
221
+ </div>
222
+ );
223
+ }
224
+ ```
225
+
226
+ **Возвращаемое значение (ResourceRefInstance):**
227
+
228
+ | Метод | Описание |
229
+ |-------|----------|
230
+ | `has` | Проверка наличия элемента в кэше |
231
+ | `lock()` | Блокировка ресурса, возвращает `{ unlock }` |
232
+ | `unlockOne()` | Снятие одной блокировки |
233
+ | `patch(fn)` | Создание patch-транзакции |
234
+ | `invalidate()` | Инвалидация кэша |
235
+ | `create(data)` | Создание элемента в кэше |
236
+
237
+
238
+ ---
239
+
240
+ ## Паттерны использования
241
+
242
+ ### Store класс
243
+
244
+ ```tsx
245
+ import { Signal, useSignal } from '@fozy-labs/rx-toolkit';
246
+
247
+ class CounterStore {
248
+ count$ = Signal.state(0, 'counter');
249
+ doubled$ = Signal.compute(() => this.count$() * 2);
250
+
251
+ increment = () => this.count$.set(this.count$() + 1);
252
+ decrement = () => this.count$.set(this.count$() - 1);
253
+ reset = () => this.count$.set(0);
254
+ }
255
+
256
+ // Singleton
257
+ const counterStore = new CounterStore();
258
+
259
+ function Counter() {
260
+ const count = useSignal(counterStore.count$);
261
+ const doubled = useSignal(counterStore.doubled$);
262
+
263
+ return (
264
+ <div>
265
+ <p>{count} × 2 = {doubled}</p>
266
+ <button onClick={counterStore.increment}>+</button>
267
+ <button onClick={counterStore.decrement}>-</button>
268
+ <button onClick={counterStore.reset}>Reset</button>
269
+ </div>
270
+ );
271
+ }
272
+ ```
273
+
274
+ ### Условные запросы
275
+
276
+ ```tsx
277
+ function UserStats({ userId, showStats }) {
278
+ const statsQuery = useResourceAgent(
279
+ statsResource,
280
+ showStats && userId ? { userId } : SKIP
281
+ );
282
+
283
+ if (!showStats) return null;
284
+
285
+ return <StatsDisplay data={statsQuery.data} />;
286
+ }
287
+ ```
288
+
289
+ ### Комбинирование ресурсов
290
+
291
+ ```tsx
292
+ function Dashboard() {
293
+ const userQuery = useResourceAgent(userResource, { id: currentUserId });
294
+ const settingsQuery = useResourceAgent(settingsResource, undefined);
295
+
296
+ const isLoading = userQuery.isLoading || settingsQuery.isLoading;
297
+ const isError = userQuery.isError || settingsQuery.isError;
298
+
299
+ if (isLoading) return <Loader />;
300
+ if (isError) return <Error />;
301
+
302
+ return (
303
+ <div>
304
+ <UserInfo user={userQuery.data} />
305
+ <Settings settings={settingsQuery.data} />
306
+ </div>
307
+ );
308
+ }
309
+ ```