@fozy-labs/rx-toolkit 0.5.3-rc.2 → 0.5.4
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/LICENSE +21 -21
- package/README.md +143 -137
- package/dist/common/devtools/combineDevtools.js +3 -3
- package/dist/common/devtools/index.d.ts +3 -3
- package/dist/common/devtools/index.js +3 -3
- package/dist/common/devtools/reduxDevtools.d.ts +1 -1
- package/dist/common/devtools/reduxDevtools.js +17 -17
- package/dist/common/devtools/types.d.ts +0 -6
- package/dist/common/options/SharedOptions.d.ts +1 -0
- package/dist/common/options/SharedOptions.js +6 -0
- package/dist/common/options/index.d.ts +1 -1
- package/dist/common/options/index.js +1 -1
- package/dist/common/react/index.d.ts +2 -2
- package/dist/common/react/index.js +2 -2
- package/dist/common/react/useConstant.js +1 -1
- package/dist/common/utils/deepEqual.js +1 -1
- package/dist/common/utils/index.d.ts +3 -3
- package/dist/common/utils/index.js +3 -3
- package/dist/common/utils/shallowEqual.js +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.js +8 -7
- package/dist/query/SKIP_TOKEN.js +1 -1
- package/dist/query/api/createCommand.d.ts +1 -1
- package/dist/query/api/createOperation.d.ts +1 -1
- package/dist/query/api/createOperation.js +1 -1
- package/dist/query/api/createResource.d.ts +1 -1
- package/dist/query/api/createResourceDuplicator.d.ts +1 -1
- package/dist/query/core/Command/Command.d.ts +7 -7
- package/dist/query/core/Command/Command.js +2 -2
- package/dist/query/core/Command/CommandAgent.d.ts +1 -1
- package/dist/query/core/Command/index.d.ts +2 -2
- package/dist/query/core/Command/index.js +2 -2
- package/dist/query/core/{Opertation → Operation}/Operation.d.ts +2 -2
- package/dist/query/core/{Opertation → Operation}/Operation.js +1 -1
- package/dist/query/core/{Opertation → Operation}/OperationAgent.d.ts +1 -1
- package/dist/query/core/{Opertation → Operation}/OperationAgent.js +1 -1
- package/dist/query/core/QueriesCache.d.ts +1 -1
- package/dist/query/core/QueriesCache.js +1 -1
- package/dist/query/core/QueriesLifetimeHooks.js +7 -7
- package/dist/query/core/Resource/Resource.d.ts +15 -15
- package/dist/query/core/Resource/Resource.js +7 -7
- package/dist/query/core/Resource/ResourceAgent.d.ts +1 -1
- package/dist/query/core/Resource/ResourceAgent.js +2 -2
- package/dist/query/core/Resource/ResourceDuplicator.d.ts +16 -16
- package/dist/query/core/Resource/ResourceDuplicator.js +18 -20
- package/dist/query/core/Resource/ResourceDuplicatorAgent.d.ts +5 -5
- package/dist/query/core/Resource/ResourceDuplicatorAgent.js +2 -2
- package/dist/query/core/Resource/ResourceRef.d.ts +2 -2
- package/dist/query/core/Resource/ResourceRef.js +12 -12
- package/dist/query/index.d.ts +11 -10
- package/dist/query/index.js +11 -10
- package/dist/query/lib/IndirectMap.js +4 -4
- package/dist/query/react/useCommandAgent.d.ts +2 -2
- package/dist/query/react/useOperationAgent.d.ts +1 -1
- package/dist/query/react/useOperationAgent.js +1 -1
- package/dist/query/react/useResourceAgent.d.ts +3 -3
- package/dist/query/react/useResourceAgent.js +1 -1
- package/dist/query/react/useResourceRef.d.ts +3 -3
- package/dist/query/react/useResourceRef.js +7 -2
- package/dist/query/types/Command.types.d.ts +1 -1
- package/dist/query/types/Operation.types.d.ts +1 -1
- package/dist/query/types/Resource.types.d.ts +7 -5
- package/dist/query/types/index.d.ts +4 -4
- package/dist/query/types/index.js +4 -4
- package/dist/query-v2/api/createApi.d.ts +10 -0
- package/dist/query-v2/api/createApi.js +83 -0
- package/dist/query-v2/core/common/CacheEntry.d.ts +29 -0
- package/dist/query-v2/core/common/CacheEntry.js +71 -0
- package/dist/query-v2/core/common/CacheMap.d.ts +38 -0
- package/dist/query-v2/core/common/CacheMap.js +127 -0
- package/dist/query-v2/core/common/LifecycleHooks.d.ts +22 -0
- package/dist/query-v2/core/common/LifecycleHooks.js +104 -0
- package/dist/query-v2/core/common/index.d.ts +3 -0
- package/dist/query-v2/core/common/index.js +3 -0
- package/dist/query-v2/core/index.d.ts +3 -0
- package/dist/query-v2/core/index.js +3 -0
- package/dist/query-v2/core/machines/Machine.d.ts +14 -0
- package/dist/query-v2/core/machines/Machine.js +33 -0
- package/dist/query-v2/core/machines/MachineError.d.ts +11 -0
- package/dist/query-v2/core/machines/MachineError.js +26 -0
- package/dist/query-v2/core/machines/MachineIdle.d.ts +8 -0
- package/dist/query-v2/core/machines/MachineIdle.js +19 -0
- package/dist/query-v2/core/machines/MachinePending.d.ts +12 -0
- package/dist/query-v2/core/machines/MachinePending.js +29 -0
- package/dist/query-v2/core/machines/MachineRefreshing.d.ts +14 -0
- package/dist/query-v2/core/machines/MachineRefreshing.js +46 -0
- package/dist/query-v2/core/machines/MachineSuccess.d.ts +16 -0
- package/dist/query-v2/core/machines/MachineSuccess.js +42 -0
- package/dist/query-v2/core/machines/MachineWithData.d.ts +18 -0
- package/dist/query-v2/core/machines/MachineWithData.js +40 -0
- package/dist/query-v2/core/machines/Patcher.d.ts +20 -0
- package/dist/query-v2/core/machines/Patcher.js +104 -0
- package/dist/query-v2/core/machines/index.d.ts +8 -0
- package/dist/query-v2/core/machines/index.js +8 -0
- package/dist/query-v2/core/resource/ResourceV2.d.ts +120 -0
- package/dist/query-v2/core/resource/ResourceV2.js +464 -0
- package/dist/query-v2/core/resource/ResourceV2Agent.d.ts +26 -0
- package/dist/query-v2/core/resource/ResourceV2Agent.js +132 -0
- package/dist/query-v2/core/resource/index.d.ts +2 -0
- package/dist/query-v2/core/resource/index.js +2 -0
- package/dist/query-v2/index.d.ts +11 -0
- package/dist/query-v2/index.js +17 -0
- package/dist/query-v2/lib/NO_VALUE.d.ts +2 -0
- package/dist/query-v2/lib/NO_VALUE.js +1 -0
- package/dist/query-v2/lib/SKIP_TOKEN.d.ts +2 -0
- package/dist/query-v2/lib/SKIP_TOKEN.js +1 -0
- package/dist/query-v2/lib/index.d.ts +4 -0
- package/dist/query-v2/lib/index.js +3 -0
- package/dist/query-v2/lib/stableStringify.d.ts +8 -0
- package/dist/query-v2/lib/stableStringify.js +23 -0
- package/dist/query-v2/plugins/ReactHooksPlugin.d.ts +25 -0
- package/dist/query-v2/plugins/ReactHooksPlugin.js +19 -0
- package/dist/query-v2/plugins/types.d.ts +1 -0
- package/dist/query-v2/plugins/types.js +1 -0
- package/dist/query-v2/react/__tests__/helpers.d.ts +12 -0
- package/dist/query-v2/react/__tests__/helpers.js +33 -0
- package/dist/query-v2/react/index.d.ts +2 -0
- package/dist/query-v2/react/index.js +2 -0
- package/dist/query-v2/react/useResourceV2Agent.d.ts +12 -0
- package/dist/query-v2/react/useResourceV2Agent.js +36 -0
- package/dist/query-v2/react/useResourceV2Ref.d.ts +12 -0
- package/dist/query-v2/react/useResourceV2Ref.js +57 -0
- package/dist/query-v2/snapshot/Snapshot.d.ts +13 -0
- package/dist/query-v2/snapshot/Snapshot.js +76 -0
- package/dist/query-v2/types/agent.types.d.ts +54 -0
- package/dist/query-v2/types/agent.types.js +1 -0
- package/dist/query-v2/types/api.types.d.ts +22 -0
- package/dist/query-v2/types/api.types.js +1 -0
- package/dist/query-v2/types/cache.types.d.ts +37 -0
- package/dist/query-v2/types/cache.types.js +1 -0
- package/dist/query-v2/types/index.d.ts +9 -0
- package/dist/query-v2/types/index.js +9 -0
- package/dist/query-v2/types/lifecycle.types.d.ts +25 -0
- package/dist/query-v2/types/lifecycle.types.js +1 -0
- package/dist/query-v2/types/machine.types.d.ts +67 -0
- package/dist/query-v2/types/machine.types.js +1 -0
- package/dist/query-v2/types/plugin.types.d.ts +38 -0
- package/dist/query-v2/types/plugin.types.js +1 -0
- package/dist/query-v2/types/resource.types.d.ts +35 -0
- package/dist/query-v2/types/resource.types.js +1 -0
- package/dist/query-v2/types/shared.types.d.ts +20 -0
- package/dist/query-v2/types/shared.types.js +1 -0
- package/dist/query-v2/types/snapshot.types.d.ts +21 -0
- package/dist/query-v2/types/snapshot.types.js +1 -0
- package/dist/signals/base/Batcher.js +9 -5
- package/dist/signals/base/ComputeCache.js +3 -3
- package/dist/signals/base/DependencyTracker.js +1 -1
- package/dist/signals/base/Devtools.d.ts +3 -2
- package/dist/signals/base/Devtools.js +54 -27
- package/dist/signals/base/Indexer.js +1 -1
- package/dist/signals/base/ReadonlySignal.js +1 -1
- package/dist/signals/base/SyncObservable.d.ts +1 -2
- package/dist/signals/base/SyncObservable.js +2 -5
- package/dist/signals/base/index.d.ts +6 -6
- package/dist/signals/base/index.js +6 -6
- package/dist/signals/index.d.ts +5 -4
- package/dist/signals/index.js +5 -4
- package/dist/signals/operators/index.d.ts +1 -1
- package/dist/signals/operators/index.js +1 -1
- package/dist/signals/react/index.d.ts +1 -1
- package/dist/signals/react/index.js +1 -1
- package/dist/signals/signals/Computed.d.ts +3 -4
- package/dist/signals/signals/Computed.js +18 -10
- package/dist/signals/signals/Effect.js +2 -1
- package/dist/signals/signals/LocalState.d.ts +3 -4
- package/dist/signals/signals/LocalState.js +8 -8
- package/dist/signals/signals/Signal.d.ts +7 -6
- package/dist/signals/signals/Signal.js +4 -1
- package/dist/signals/signals/State.d.ts +4 -5
- package/dist/signals/signals/State.js +23 -9
- package/dist/signals/signals/index.d.ts +5 -5
- package/dist/signals/signals/index.js +5 -6
- package/dist/signals/types/SignalOptions.d.ts +16 -0
- package/dist/signals/types/SignalOptions.js +1 -0
- package/dist/signals/types/index.d.ts +3 -1
- package/dist/signals/types/index.js +3 -1
- package/dist/signals/types/normalizeSignalOptions.d.ts +2 -0
- package/dist/signals/types/normalizeSignalOptions.js +10 -0
- package/dist/signals/types/signals.types.d.ts +2 -3
- package/docs/CHANGELOG.md +95 -90
- package/docs/CONTRIBUTING.md +230 -0
- package/docs/contributing/ai-assisted-development.md +47 -0
- package/docs/contributing/query-v2/README.md +379 -0
- package/docs/{release → contributing/release}/README.md +59 -59
- package/docs/devtools/README.md +228 -228
- package/docs/migrations/0.5.0.md +58 -58
- package/docs/migrations/query-v2.md +171 -0
- package/docs/options/README.md +92 -92
- package/docs/query/README.md +575 -573
- package/docs/query-v2/README.md +280 -0
- package/docs/query-v2/api-reference.md +235 -0
- package/docs/query-v2/optimistic-updates.md +148 -0
- package/docs/query-v2/ssr.md +130 -0
- package/docs/signals/README.md +300 -300
- package/docs/usage/react/README.md +309 -309
- package/package.json +85 -63
|
@@ -1,309 +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.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
|
-
**Возвращаемое значение (
|
|
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
|
-
```
|
|
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
|
+
```
|