@astral/mobx-query 1.8.1 → 1.10.0
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/MobxQuery/MobxQuery.d.ts +44 -0
- package/MobxQuery/MobxQuery.js +48 -0
- package/README.md +1048 -287
- package/Sets/InfiniteQuerySet/InfiniteQuerySet.d.ts +60 -0
- package/Sets/InfiniteQuerySet/InfiniteQuerySet.js +71 -0
- package/Sets/InfiniteQuerySet/index.d.ts +1 -0
- package/Sets/InfiniteQuerySet/index.js +1 -0
- package/Sets/MutationSet/MutationSet.d.ts +32 -0
- package/Sets/MutationSet/MutationSet.js +31 -0
- package/Sets/MutationSet/index.d.ts +1 -0
- package/Sets/MutationSet/index.js +1 -0
- package/Sets/QuerySet/QuerySet.d.ts +64 -0
- package/Sets/QuerySet/QuerySet.js +72 -0
- package/Sets/QuerySet/index.d.ts +1 -0
- package/Sets/QuerySet/index.js +1 -0
- package/Sets/index.d.ts +3 -0
- package/Sets/index.js +3 -0
- package/Sets/utils/generateQuerySetBaseKey/generateQuerySetBaseKey.d.ts +4 -0
- package/Sets/utils/generateQuerySetBaseKey/generateQuerySetBaseKey.js +5 -0
- package/Sets/utils/generateQuerySetBaseKey/index.d.ts +1 -0
- package/Sets/utils/generateQuerySetBaseKey/index.js +1 -0
- package/Sets/utils/generateQuerySetKeys/generateQuerySetKeys.d.ts +9 -0
- package/Sets/utils/generateQuerySetKeys/generateQuerySetKeys.js +20 -0
- package/Sets/utils/generateQuerySetKeys/index.d.ts +1 -0
- package/Sets/utils/generateQuerySetKeys/index.js +1 -0
- package/Sets/utils/index.d.ts +2 -0
- package/Sets/utils/index.js +2 -0
- package/index.d.ts +2 -1
- package/index.js +1 -0
- package/node/MobxQuery/MobxQuery.d.ts +44 -0
- package/node/MobxQuery/MobxQuery.js +48 -0
- package/node/Sets/InfiniteQuerySet/InfiniteQuerySet.d.ts +60 -0
- package/node/Sets/InfiniteQuerySet/InfiniteQuerySet.js +75 -0
- package/node/Sets/InfiniteQuerySet/index.d.ts +1 -0
- package/node/Sets/InfiniteQuerySet/index.js +17 -0
- package/node/Sets/MutationSet/MutationSet.d.ts +32 -0
- package/node/Sets/MutationSet/MutationSet.js +35 -0
- package/node/Sets/MutationSet/index.d.ts +1 -0
- package/node/Sets/MutationSet/index.js +17 -0
- package/node/Sets/QuerySet/QuerySet.d.ts +64 -0
- package/node/Sets/QuerySet/QuerySet.js +76 -0
- package/node/Sets/QuerySet/index.d.ts +1 -0
- package/node/Sets/QuerySet/index.js +17 -0
- package/node/Sets/index.d.ts +3 -0
- package/node/Sets/index.js +19 -0
- package/node/Sets/utils/generateQuerySetBaseKey/generateQuerySetBaseKey.d.ts +4 -0
- package/node/Sets/utils/generateQuerySetBaseKey/generateQuerySetBaseKey.js +12 -0
- package/node/Sets/utils/generateQuerySetBaseKey/index.d.ts +1 -0
- package/node/Sets/utils/generateQuerySetBaseKey/index.js +17 -0
- package/node/Sets/utils/generateQuerySetKeys/generateQuerySetKeys.d.ts +9 -0
- package/node/Sets/utils/generateQuerySetKeys/generateQuerySetKeys.js +24 -0
- package/node/Sets/utils/generateQuerySetKeys/index.d.ts +1 -0
- package/node/Sets/utils/generateQuerySetKeys/index.js +17 -0
- package/node/Sets/utils/index.d.ts +2 -0
- package/node/Sets/utils/index.js +18 -0
- package/node/index.d.ts +2 -1
- package/node/index.js +3 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -3,414 +3,704 @@
|
|
|
3
3
|
Библиотека для кеширования запросов.
|
|
4
4
|
|
|
5
5
|
Особенности:
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
- ⚡️️️️ Реактивный кэш на основе [mobx](https://mobx.js.org/)
|
|
7
|
+
- ️️️️️️⚡️️️️ Вдохновлено [@tanstack/react-query](https://www.npmjs.com/package/@tanstack/react-query/)
|
|
8
|
+
- ⚡️️️️ Декларативный способ описания queries и mutations
|
|
9
|
+
- ⚡️️️️ Реализация архитектурного подхода работы с данными [Astral Architecture Guide](https://guides.frontend.cloud.astral-dev.ru/docs/arch/intro)
|
|
10
|
+
- ⚡️️️️ Фоновая подгрузка данных для работы с WebSocket
|
|
11
|
+
- ⚡️️️️ Возможность тестирования
|
|
12
12
|
|
|
13
13
|
## Table of contents
|
|
14
|
+
|
|
14
15
|
- [Installation](#installation)
|
|
15
|
-
- [Basic
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- [
|
|
21
|
-
|
|
22
|
-
- [
|
|
23
|
-
- [
|
|
24
|
-
|
|
25
|
-
- [
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- [
|
|
29
|
-
- [
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
- [
|
|
34
|
-
- [
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
16
|
+
- [Basic usage](#basic-usage)
|
|
17
|
+
- [Core concepts](#core-concepts)
|
|
18
|
+
- [QuerySet](#queryset)
|
|
19
|
+
- [Создание QuerySet](#создание-queryset)
|
|
20
|
+
- [Использование QuerySet](#использование-queryset)
|
|
21
|
+
- [Синхронный вызов запроса данных](#синхронный-вызов-запроса-данных)
|
|
22
|
+
- [Асинхронный вызов запроса данных](#асинхронный-вызов-запроса-данных)
|
|
23
|
+
- [Автоматический запрос данных](#автоматический-запрос-данных)
|
|
24
|
+
- [InfiniteQuerySet](#infinitequerygroups)
|
|
25
|
+
- [Создание InfiniteQuerySet](#создание-infinitequerygroups)
|
|
26
|
+
- [Использование InfiniteQuerySet](#использование-infinitequerygroups)
|
|
27
|
+
- [Загрузка данных](#загрузка-данных)
|
|
28
|
+
- [isEndReached. Определение конца списка](#isendreached-определение-конца-списка)
|
|
29
|
+
- [Изменение количества запрашиваемых записей](#изменение-количества-запрашиваемых-записей)
|
|
30
|
+
- [MutationSet](#mutationset)
|
|
31
|
+
- [Создание mutations](#создание-и-использование-mutations)
|
|
32
|
+
- [Синхронный вызов mutation](#синхронный-вызов-mutation)
|
|
33
|
+
- [Асинхронный вызов mutation](#асинхронный-вызов-mutation)
|
|
34
|
+
- [Кэширование QuerySet и InfiniteQuerySet](#кэширование-queryset-и-infinitequeryset)
|
|
35
|
+
- [FetchPolicy](#fetchpolicy)
|
|
36
|
+
- [Как работает кэш](#как-работает-кэш)
|
|
37
|
+
- [Инвалидация кэша QuerySet](#инвалидация-кэша-queryset)
|
|
38
|
+
- [Кастомная установка ключей кэширования](#кастомная-установка-ключей-кэширования)
|
|
39
|
+
- [Фоновая загрузка данных QuerySet и InfiniteQuerySet](#фоновая-загрузка-данных-queryset-и-infinitequeryset)
|
|
40
|
+
- [Изменение кэша QuerySet и InfiniteQuerySet](#изменение-кэша-queryset-и-infinitequeryset)
|
|
41
|
+
- [Тестирование QuerySet и InfiniteQuerySet](#тестирование-queryset-и-infinitequeryset)
|
|
42
|
+
- [Core](#core)
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install @astral/mobx-query --save
|
|
45
48
|
```
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
```bash
|
|
51
|
+
yarn add @astral/mobx-query
|
|
52
|
+
```
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
- executor - исполнитель запроса, который будет совершать запрос. Второй аргумент при создании query
|
|
51
|
-
- enabledAutoFetch - включает автоматический запрос данных при обращении к полю `data`.
|
|
52
|
-
- fetchPolicy - политика, говорящая о том, как следует работать с новыми запросами
|
|
53
|
-
- 'cache-first' - политика применяемая по умолчанию, при отсутствии данных в памяти, будет исполнен executor, его ответ запишется в кеш, и при последующих обращениях данные будут взяты из кеша
|
|
54
|
-
- 'network-only' - каждый запрос будет приводить к вызову executor, его ответ будет записан в кеш(для использования в cache-first)
|
|
54
|
+
## Basic usage
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
```
|
|
57
|
+
├── api/
|
|
58
|
+
| ├── _fakers/
|
|
59
|
+
| ├── endpoints/
|
|
60
|
+
| ├── fetchers/
|
|
61
|
+
| | ├── docs.ts
|
|
62
|
+
| | └── index.ts
|
|
63
|
+
| ├── services/
|
|
64
|
+
| | ├── CacheService/
|
|
65
|
+
| | | ├── CacheService.ts
|
|
66
|
+
| | | └── index.ts
|
|
67
|
+
| | └── index.ts
|
|
68
|
+
| └── index.ts
|
|
69
|
+
```
|
|
57
70
|
|
|
58
|
-
|
|
71
|
+
Инициализация MobxQuery:
|
|
72
|
+
```api/services/CacheService/CacheService.ts```
|
|
59
73
|
```ts
|
|
60
74
|
import { MobxQuery } from '@astral/mobx-query';
|
|
61
75
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
enabledAutoFetch: false, // false по умолчанию, опционально
|
|
68
|
-
});
|
|
76
|
+
// рекомендуется явно задавать параметры для MobXQuery
|
|
77
|
+
export const createCacheService = () =>
|
|
78
|
+
new MobxQuery({ enableAutoFetch: true, fetchPolicy: 'cache-first' });
|
|
79
|
+
|
|
80
|
+
export const cacheService = createCacheService();
|
|
69
81
|
```
|
|
70
82
|
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
Определение fetcher и cacheGroups для docs:
|
|
84
|
+
```api/services/Fetcher/docs.ts```
|
|
85
|
+
```ts
|
|
86
|
+
const docsFetcher = {
|
|
87
|
+
queries: {
|
|
88
|
+
doc: cacheService.createQuerySet((id: string) => ({
|
|
89
|
+
execute: () => docsEndpoint.getDoc(id).then(({ data }) => data),
|
|
90
|
+
})),
|
|
91
|
+
},
|
|
92
|
+
infiniteQueries: {
|
|
93
|
+
docList: cacheService.createInfiniteQuerySet(
|
|
94
|
+
(filters: Omit<DocsEndpointsDTO.DocListFilters, 'offset' | 'count'>) => ({
|
|
95
|
+
execute: ({ offset, count }) =>
|
|
96
|
+
docsEndpoint
|
|
97
|
+
.getDocList({ offset, count, ...filters })
|
|
98
|
+
.then(({ data }) => data.list),
|
|
99
|
+
}),
|
|
100
|
+
),
|
|
101
|
+
},
|
|
102
|
+
mutations: {
|
|
103
|
+
editDoc: cacheService.createMutationSet(
|
|
104
|
+
(params: DocsEndpointsDTO.EditDocInput) => docsEndpoint.editDoc(params),
|
|
105
|
+
),
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export type DocsFetcher = typeof docsFetcher;
|
|
110
|
+
```
|
|
73
111
|
|
|
74
|
-
|
|
75
|
-
Если при вызове обработчик `onError` не был передан, вызовется стандартный, переданный при создании MobxQuery инстанса.
|
|
112
|
+
Использование в store:
|
|
76
113
|
|
|
77
114
|
```ts
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
115
|
+
import { type DocsFetcher } from '@example/api';
|
|
116
|
+
|
|
117
|
+
class DocStore {
|
|
118
|
+
constructor(
|
|
119
|
+
private readonly _docID: string,
|
|
120
|
+
private readonly _docsFetcher: DocsFetcher,
|
|
121
|
+
) {
|
|
122
|
+
makeAutoObservable(this, {}, { autoBind: true });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private get docQuery() {
|
|
126
|
+
return this._docsFetcher.queries.doc.create(this.docID);
|
|
127
|
+
}
|
|
82
128
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
},
|
|
87
|
-
onError: (error) => {
|
|
88
|
-
console.log(error); // место для вашей ошибки
|
|
129
|
+
public get docName() {
|
|
130
|
+
if (!this.docQuery.data) {
|
|
131
|
+
return '';
|
|
89
132
|
}
|
|
133
|
+
|
|
134
|
+
return `Название документа: ${this.docQuery.data.name}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public get isLoading() {
|
|
138
|
+
return this.docQuery.isLoading;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
class DocManagerStore {
|
|
145
|
+
constructor(private readonly _docsFetcher: DocsFetcher) {}
|
|
146
|
+
|
|
147
|
+
public changeOrg = () => {
|
|
148
|
+
// инвалидирует все doc query, которые есть в кэше
|
|
149
|
+
this._docsFetcher.queries.doc.invalidate();
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Core concepts
|
|
155
|
+
|
|
156
|
+
[`Query`](#query) предназначен для получения данных. Не должен производить изменения.
|
|
157
|
+
[`QuerySet`](#queryset) - набор queries для данных, получаемых по одной и той же сущности с разными параметрами.
|
|
158
|
+
[`InfiniteQuery`](#infinitequery) предназначен для получения бесконечного списка данных.
|
|
159
|
+
[`Mutation`](#mutation) предназначен для изменения данных на сервере.
|
|
160
|
+
[`MutationSet`](#mutationset) - набор mutations для изменения данных. Необходим для консистенстности api с `QuerySet`.
|
|
161
|
+
|
|
162
|
+
`QuerySet`, `InfiniteQuerySet` и `MutationSet` создаются через методы MobxQuery:
|
|
163
|
+
- `createQuerySet`
|
|
164
|
+
- `createInfiniteQuerySet`
|
|
165
|
+
- `createMutationSet`
|
|
166
|
+
|
|
167
|
+
`Query`, `InfiniteQuery` и `Mutation` создаются через методы MobxQuery:
|
|
168
|
+
- `createQuery`
|
|
169
|
+
- `createInfiniteQuery`
|
|
170
|
+
- `createMutation`
|
|
171
|
+
|
|
172
|
+
Рекомендуется использовать именно Set'ы потому что это выскоуровневое api, скрывающее внутри себя сложность работы с кэшем и позволяющее работать с данными более декларативно. Set'ы используют внутри себя `Query`, `InfiniteQuery` и `Mutation`.
|
|
173
|
+
|
|
174
|
+
Методы `createQuery`, `createInfiniteQuery` и `createMutation` необходимо использовать для реализации собственных библиотек для работы с данными.
|
|
175
|
+
|
|
176
|
+
## QuerySet
|
|
177
|
+
|
|
178
|
+
[`Query`](#query) предназначен для получения данных. Не должен производить изменения.
|
|
179
|
+
`QuerySet` - набор queries для данных, получаемых по одной и той же сущности с разными параметрами.
|
|
180
|
+
|
|
181
|
+
### Создание QuerySet
|
|
182
|
+
|
|
183
|
+
В примере ниже будет создан doc query, который будет получать данные по id документа. После успешного выполнения запроса данные будут закэшированы.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const docsFetcher = {
|
|
187
|
+
queries: {
|
|
188
|
+
doc: mobxQuery.createQuerySet((id: string) => ({
|
|
189
|
+
execute: () => docsEndpoint.getDoc(id).then(({ data }) => data),
|
|
190
|
+
})),
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Использование QuerySet
|
|
196
|
+
|
|
197
|
+
Для получения объекта query необходимо вызвать метод `create`.
|
|
198
|
+
Параметры `create` полностью идентичны параметрам, указанным при определении query:
|
|
199
|
+
```ts
|
|
200
|
+
const docsFetcher = {
|
|
201
|
+
queries: {
|
|
202
|
+
doc: mobxQuery.createQuerySet((id: string) => ({
|
|
203
|
+
execute: () => docsEndpoint.getDoc(id).then(({ data }) => data),
|
|
204
|
+
})),
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// create принимает только один параметр - id: string
|
|
209
|
+
const docQuery = docsFetcher.queries.doc.create('docID');
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Метод `create` вернет [объект query](#query-interface), который содержит всю информацию по запросу и методы работы с запросом.
|
|
213
|
+
|
|
214
|
+
### Синхронный вызов запроса данных
|
|
215
|
+
|
|
216
|
+
Метод `sync` позволяет синхронно запустить запрос на получение данных:
|
|
217
|
+
```ts
|
|
218
|
+
const docQuery = docsFetcher.queries.doc.create(docID);
|
|
219
|
+
|
|
220
|
+
// есть callbacks на обработку success и error
|
|
221
|
+
docQuery.sync({
|
|
222
|
+
onSuccess: (data) => {
|
|
223
|
+
console.log(data);
|
|
224
|
+
},
|
|
225
|
+
onError: (error) => {
|
|
226
|
+
console.log(error);
|
|
227
|
+
},
|
|
90
228
|
});
|
|
229
|
+
|
|
230
|
+
// true
|
|
231
|
+
docQuery.isLoading;
|
|
91
232
|
```
|
|
92
233
|
|
|
93
|
-
|
|
234
|
+
### Асинхронный вызов запроса данных
|
|
94
235
|
|
|
95
|
-
|
|
236
|
+
Метод `async` позволяет асинхронно запустить запрос на получение данных:
|
|
237
|
+
```ts
|
|
238
|
+
const docQuery = docsFetcher.queries.doc.create(docID);
|
|
239
|
+
|
|
240
|
+
// передавать параметры запроса не нужно потому что они уже были переданы при вызове init
|
|
241
|
+
const response = await docQuery.async();
|
|
242
|
+
|
|
243
|
+
docQuery.isSuccess; // true
|
|
244
|
+
docQuery.data; // идентичен response
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Автоматический запрос данных
|
|
96
248
|
|
|
97
|
-
|
|
98
|
-
`Будьте внимательны, используя метод "async", позаботьтесь о добавлении ".catch", иначе ошибка запроса попадет в глобальный exception`.
|
|
249
|
+
Если MobxQuery был создан с флагом `enableAutoFetch: true`, то данные будут автоматически запрошены при обращении к полю `data`:
|
|
99
250
|
|
|
100
251
|
```ts
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
252
|
+
const docQuery = docsFetcher.queries.doc.create(docID);
|
|
253
|
+
|
|
254
|
+
docQuery.data; // триггер запроса данных
|
|
255
|
+
|
|
256
|
+
docQuery.isLoading; // true
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Если MobxQuery был создан с флагом `enableAutoFetch: false`, то автоматически запрос данных можно включить для текущего query:
|
|
260
|
+
```ts
|
|
261
|
+
const docQuery = docsFetcher.queries.doc.createWithConfig((
|
|
262
|
+
{ enableAutoFetch: true },
|
|
263
|
+
docID
|
|
104
264
|
);
|
|
105
265
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
console.log(data); // место для реагирования на ответ
|
|
110
|
-
})
|
|
111
|
-
.catch((e) => {
|
|
112
|
-
console.log(e); // место для вашей ошибки
|
|
113
|
-
});
|
|
266
|
+
docQuery.data; // триггер запроса данных
|
|
267
|
+
|
|
268
|
+
docQuery.isLoading; // true
|
|
114
269
|
```
|
|
115
|
-
**[Пример в sandbox](https://codesandbox.io/s/mobx-query-simple-async-k75tfg)**
|
|
116
270
|
|
|
117
|
-
##
|
|
271
|
+
## InfiniteQuerySet
|
|
118
272
|
|
|
119
|
-
|
|
120
|
-
|
|
273
|
+
[`InfiniteQuery`](#infinitequery) предназначен для получения бесконечного списка данных.
|
|
274
|
+
`InfiniteQuerySet` - набор InfiniteQuery для данных, получаемых по одной и той же сущности с разными параметрами.
|
|
121
275
|
|
|
122
|
-
|
|
123
|
-
import { observer } from 'mobx-react-lite';
|
|
276
|
+
### Создание InfiniteQuerySet
|
|
124
277
|
|
|
125
|
-
|
|
126
|
-
['some cache key'],
|
|
127
|
-
() => Promise.resolve('foo'),
|
|
128
|
-
{ enabledAutoFetch: true }
|
|
129
|
-
);
|
|
278
|
+
В примере ниже будет создан docList InfiniteQuerySet. После успешного выполнения запроса данные будут закэшированы.
|
|
130
279
|
|
|
131
|
-
|
|
280
|
+
```ts
|
|
281
|
+
const docsFetcher = {
|
|
282
|
+
infiniteQueries: {
|
|
283
|
+
docList: mobxQuery.createInfiniteQuerySet(
|
|
284
|
+
(filters: Omit<DocsEndpointsDTO.DocListFilters, 'offset' | 'count'>) => ({
|
|
285
|
+
execute: ({ offset, count }) =>
|
|
286
|
+
docsEndpoint
|
|
287
|
+
.getDocList({ offset, count, ...filters })
|
|
288
|
+
.then(({ data }) => data.list),
|
|
289
|
+
}),
|
|
290
|
+
),
|
|
291
|
+
},
|
|
292
|
+
};
|
|
132
293
|
```
|
|
133
|
-
**[Пример в sandbox](https://codesandbox.io/s/happy-cherry-ytqgry)**
|
|
134
294
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
295
|
+
### Использование InfiniteQuerySet
|
|
296
|
+
|
|
297
|
+
Для получения объекта `InfiniteQuery` необходимо вызвать метод `create`.
|
|
298
|
+
Параметры `create` полностью идентичны параметрам, указанным при определении InfiniteQuery:
|
|
299
|
+
```ts
|
|
300
|
+
const docsFetcher = {
|
|
301
|
+
infiniteQueries: {
|
|
302
|
+
docList: mobxQuery.createInfiniteQuerySet(
|
|
303
|
+
// Omit необходим для того, чтобы не передавать offset и count при вызове запроса из логики. Offset и count будут сформированы и переданы автоматически
|
|
304
|
+
(filters: Omit<DocsEndpointsDTO.DocListFilters, 'offset' | 'count'>) => ({
|
|
305
|
+
execute: ({ offset, count }) =>
|
|
306
|
+
docsEndpoint
|
|
307
|
+
.getDocList({ offset, count, ...filters })
|
|
308
|
+
// fetch должен возвращать array
|
|
309
|
+
.then(({ data }) => data.list),
|
|
310
|
+
}),
|
|
311
|
+
),
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// первым параметром create является - filters: Omit<DocsEndpointsDTO.DocListFilters, 'offset' | 'count'>
|
|
316
|
+
const docListQuery = docsFetcher.infiniteQueries.docList.create({ search: 'test' });
|
|
317
|
+
```
|
|
140
318
|
|
|
141
|
-
|
|
319
|
+
### Загрузка данных
|
|
142
320
|
|
|
143
321
|
```ts
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
);
|
|
322
|
+
import { when } from 'mobx';
|
|
323
|
+
|
|
324
|
+
const docListQuery = docsFetcher.infiniteQueries.docList.create({ search: 'test' });
|
|
325
|
+
|
|
326
|
+
await docListQuery.async(); // запрос на получение первых 30 записей
|
|
149
327
|
|
|
150
|
-
|
|
328
|
+
docListQuery.data.length; // 30 записей
|
|
329
|
+
|
|
330
|
+
docListQuery.fetchMore(); // запрос на получение следующих 30 записей
|
|
331
|
+
|
|
332
|
+
// ждем загрузки
|
|
333
|
+
await when(() => !query.isLoading);
|
|
334
|
+
|
|
335
|
+
docListQuery.data.length; // 60 записей
|
|
151
336
|
```
|
|
152
337
|
|
|
153
|
-
|
|
338
|
+
### `isEndReached`. Определение конца списка
|
|
154
339
|
|
|
155
|
-
|
|
156
|
-
- Как при создании query, так и при инвалидации, нужно использовать массив ключей. Предполагается, что query может быть инвалидирован по нескольким ключам
|
|
340
|
+
Флаг `isEndReached` будет установлен в `true`, если записи для загрузки закончились:
|
|
157
341
|
```ts
|
|
158
|
-
|
|
159
|
-
['key one', 'key two'], // ключ - массив строк
|
|
160
|
-
() => Promise.resolve('foo'),
|
|
161
|
-
{ enabledAutoFetch: true }
|
|
162
|
-
);
|
|
342
|
+
import { when } from 'mobx';
|
|
163
343
|
|
|
164
|
-
|
|
165
|
-
|
|
344
|
+
const docListQuery = docsFetcher.infiniteQueries.docList.create({ search: 'test' });
|
|
345
|
+
|
|
346
|
+
await docListQuery.async(); // запрос на получение первых 30 записей
|
|
347
|
+
|
|
348
|
+
docListQuery.data.length; // 30 записей
|
|
349
|
+
|
|
350
|
+
docListQuery.fetchMore(); // запрос на получение следующих 30 записей
|
|
351
|
+
|
|
352
|
+
// ждем загрузки
|
|
353
|
+
await when(() => !query.isLoading);
|
|
354
|
+
|
|
355
|
+
docListQuery.data.length; // 19 записей. Последняя страница содержит 9 записей, а не 30, как было запрошено, значит больше данных нет
|
|
356
|
+
|
|
357
|
+
docListQuery.isEndReached; // true
|
|
166
358
|
```
|
|
167
|
-
|
|
359
|
+
|
|
360
|
+
Флаг `isEndReached` устанавливается в `true`, когда количество полученных элементов меньше запрошенного количества, что означает отсутствие дополнительных данных на сервере.
|
|
361
|
+
|
|
362
|
+
### Изменение количества запрашиваемых записей
|
|
363
|
+
|
|
364
|
+
Для изменения конфигурации InfiniteQuery необходимо использовать метод `createWithConfig`:
|
|
168
365
|
```ts
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
{ enabledAutoFetch: true }
|
|
366
|
+
const docListQuery = docsFetcher.infiniteQueries.docList.createWithConfig(
|
|
367
|
+
{ incrementCount: 10, enabledAutoFetch: true },
|
|
368
|
+
{ search: 'test' },
|
|
173
369
|
);
|
|
370
|
+
```
|
|
174
371
|
|
|
175
|
-
|
|
372
|
+
[Интерфейс `InfiniteQueryConfig`](#интерфейс-infinitequeryconfig).
|
|
373
|
+
|
|
374
|
+
## MutationSet
|
|
375
|
+
|
|
376
|
+
[`Mutation`](#mutation) предназначен для отправки данных на сервер.
|
|
377
|
+
`MutationSet` - набор mutations, изменяющих одну и ту же сущность. Предоставляет интерфейс, идентичный `QuerySet`, для консистенстности api.
|
|
378
|
+
|
|
379
|
+
### Создание и использование MutationSet
|
|
380
|
+
|
|
381
|
+
В примере ниже будет создан `editDoc` mutation, который отправляет данные на сервер для редактирования документа:
|
|
382
|
+
```ts
|
|
383
|
+
const docsFetcher = {
|
|
384
|
+
mutations: {
|
|
385
|
+
editDoc: mobxQuery.createMutationSet((params: DocsEndpointsDTO.EditDocInput) => docsEndpoint.editDoc(params)),
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const editDocMutation = docsFetcher.mutations.editDoc.create();
|
|
390
|
+
|
|
391
|
+
// params являются параметрами, указанными при определении mutation - DocsEndpointsDTO.EditDocInput
|
|
392
|
+
await editDocMutation.async({ params: { id: 'docID', name: 'test' } });
|
|
176
393
|
```
|
|
177
394
|
|
|
178
|
-
|
|
395
|
+
Метод `create` вернет [объект mutation](#интерфейс-mutation), который содержит всю информацию по запросу и методы работы с запросом.
|
|
179
396
|
|
|
180
|
-
###
|
|
181
|
-
|
|
397
|
+
### Синхронный вызов mutation
|
|
398
|
+
|
|
399
|
+
Метод `sync` позволяет синхронно запустить запрос:
|
|
182
400
|
```ts
|
|
183
|
-
|
|
401
|
+
const editDocMutation = docsFetcher.mutations.editDoc.create();
|
|
402
|
+
|
|
403
|
+
// есть callbacks на обработку success и error
|
|
404
|
+
editDocMutation.sync({
|
|
405
|
+
params: { id: 'docID', name: 'test' },
|
|
406
|
+
onSuccess: (data) => {
|
|
407
|
+
console.log(data);
|
|
408
|
+
},
|
|
409
|
+
onError: (error) => {
|
|
410
|
+
console.log(error);
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// true
|
|
415
|
+
editDocMutation.isLoading;
|
|
184
416
|
```
|
|
185
417
|
|
|
186
|
-
|
|
418
|
+
### Асинхронный вызов mutation
|
|
187
419
|
|
|
188
|
-
|
|
420
|
+
Метод `async` позволяет асинхронно запустить запрос:
|
|
421
|
+
```ts
|
|
422
|
+
const editDocMutation = docsFetcher.mutations.editDoc.create();
|
|
189
423
|
|
|
424
|
+
await editDocMutation.async({ params: { id: 'docID', name: 'test' } });
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
## Кэширование QuerySet и InfiniteQuerySet
|
|
428
|
+
|
|
429
|
+
`QuerySet` и `InfiniteQuerySet` позволяет закэшировать данные, которые были получены ранее.
|
|
430
|
+
|
|
431
|
+
### FetchPolicy
|
|
432
|
+
|
|
433
|
+
`FetchPolicy` определяет политику получения данных.
|
|
434
|
+
|
|
435
|
+
Существует два типа политики:
|
|
436
|
+
- `cache-first` - если в кэше есть данные, они будут возвращены, если нет, то данные будут получены из сети, после чего ответ будет записан в кэш
|
|
437
|
+
- `network-only` - данные всегда берутся из сети, при этом ответ записывается в кэш
|
|
438
|
+
|
|
439
|
+
#### Глобальная установка fetchPolicy
|
|
440
|
+
|
|
441
|
+
Для глобальной установки конкретной политики fetchPolicy необходимо передать параметр `fetchPolicy` при создании `MobxQuery`:
|
|
190
442
|
```ts
|
|
191
|
-
query
|
|
443
|
+
import { MobxQuery } from 'mobx-query';
|
|
444
|
+
|
|
445
|
+
export const createQuery = () => new MobxQuery({ fetchPolicy: 'cache-first' });
|
|
192
446
|
```
|
|
193
447
|
|
|
194
|
-
|
|
195
|
-
Существует необходимость постепенного запроса массивов данных, в постраничном режиме. Типичный пример, инфинити скролл, когда новая пачка данных запрашивается, в момент когда пользователь докрутил список до конца.
|
|
196
|
-
Для удобства, мы создали специальный `query`, который содержит дополнительный метод `fetchMore` и при вызове оного, происходит запрос с увеличенными счетчиками. Данные ответа на этот запрос, будут сконкатенированы с уже имеющимся. В случае, если количество данных меньше, чем длина страницы, будет считаться что мы дошли до конца списка.
|
|
197
|
-
В `executor` будет передан объект с `offset` - количество элементов отступа от начала списка, и `count` - количество элементов на одну страницу.
|
|
448
|
+
#### Локальная установка fetchPolicy
|
|
198
449
|
|
|
199
|
-
|
|
450
|
+
Для каждого отдельного query можно установить свою fetchPolicy при инициализации.
|
|
200
451
|
|
|
452
|
+
Пример для `Query`:
|
|
201
453
|
```ts
|
|
202
|
-
|
|
454
|
+
const docQuery = docsFetcher.queries.doc.createWithConfig(({ fetchPolicy: 'network-only' }, 'docID');
|
|
455
|
+
```
|
|
203
456
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
incrementCount: 30, // опционально, по умолчанию 30
|
|
212
|
-
}
|
|
457
|
+
Пример для `InfiniteQuery`:
|
|
458
|
+
```ts
|
|
459
|
+
const docListQuery = docsFetcher.infiniteQueries.docList.createWithConfig(
|
|
460
|
+
{ incrementCount: 10, fetchPolicy: 'network-only' },
|
|
461
|
+
{ search: 'test' },
|
|
213
462
|
);
|
|
463
|
+
```
|
|
214
464
|
|
|
215
|
-
|
|
465
|
+
#### Принцип работы fetchPolicy
|
|
216
466
|
|
|
217
|
-
|
|
467
|
+
`cache-first`:
|
|
468
|
+
```ts
|
|
469
|
+
const docQuery = docsFetcher.queries.doc.createWithConfig(({ fetchPolicy: 'cache-first' }, 'docID');
|
|
218
470
|
|
|
219
|
-
|
|
220
|
-
await when(() => !query.isLoading); // ждем фоновой загрузки
|
|
471
|
+
await docQuery.async(); // запрос будет выполнен потому что до этого запроса с такими параметрами не было
|
|
221
472
|
|
|
222
|
-
|
|
223
|
-
```
|
|
224
|
-
## isEndReached
|
|
225
|
-
Для определения того, что мы все таки достигли конца списка, присутствует флаг `isEndReached`.
|
|
473
|
+
await docQuery.async(); // запрос не будет выполнен потому что данные уже есть в кэше. Promise будет завершен сразу
|
|
474
|
+
```
|
|
226
475
|
|
|
476
|
+
`network-only`:
|
|
227
477
|
```ts
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
);
|
|
478
|
+
const docQuery = docsFetcher.queries.doc.createWithConfig(({ fetchPolicy: 'network-only' }, 'docID');
|
|
479
|
+
|
|
480
|
+
await docQuery.async(); // запрос будет выполнен
|
|
481
|
+
await docQuery.async(); // запрос будет выполнен
|
|
232
482
|
|
|
233
|
-
|
|
483
|
+
const docQueryWithCache = docsFetcher.queries.doc.createWithConfig(({ fetchPolicy: 'cache-first' }, 'docID');
|
|
484
|
+
|
|
485
|
+
await docQueryWithCache.async(); // запрос не будет выполнен потому что прежде данные были получены с политикой `network-only` и записаны в кэш
|
|
486
|
+
```
|
|
234
487
|
|
|
235
|
-
|
|
236
|
-
```
|
|
488
|
+
### Как работает кэш
|
|
237
489
|
|
|
238
|
-
|
|
490
|
+
Все данные, возвращаемые `QuerySet` и `InfiniteQuerySet`, кэшируются в едином хранилище @astral/mobx-query.
|
|
239
491
|
|
|
240
|
-
|
|
241
|
-
|
|
492
|
+
При первом вызове `create` в хранилище создается запись с ключем, состоящим из:
|
|
493
|
+
- Хэш от функции конфигурации
|
|
494
|
+
- `queryParams`
|
|
242
495
|
|
|
496
|
+
Пример:
|
|
243
497
|
```ts
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
},
|
|
249
|
-
|
|
498
|
+
const docFetcher = {
|
|
499
|
+
queries: {
|
|
500
|
+
doc: mobxQuery.createQuerySet((id: string, filters: { search: string }) => ({
|
|
501
|
+
execute: () => docsEndpoint.getDoc(id, filters),
|
|
502
|
+
})),
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
// ключ кэша будет равен "12fj1d,doc,1,"{"search":"test"}""
|
|
507
|
+
// 12fj1d - это хэш от функции конфигурации
|
|
508
|
+
const docQuery = docFetcher.queries.doc.create('1', { search: 'test' });
|
|
509
|
+
// ключ кэша будет равен "12fj1d,doc,2,"{"search":"test2"}""
|
|
510
|
+
const docQuery = docsFetcher.queries.doc.create('1', { search: 'test2' });
|
|
250
511
|
```
|
|
251
|
-
**[Пример в sandbox](https://codesandbox.io/s/mobx-query-mutation-p2pnct)**
|
|
252
512
|
|
|
253
|
-
|
|
513
|
+
#### Автоматическая чистка кэша
|
|
514
|
+
|
|
515
|
+
Mobx-query кэш организован через `WeekRef`, поэтому не используемые данные автоматически удаляются сборщиком мусора.
|
|
516
|
+
|
|
517
|
+
### Инвалидация кэша QuerySet и InfiniteQuerySet
|
|
518
|
+
|
|
519
|
+
Если данные в query стали неактуальными, то необходимо вызвать метод `invalidate`:
|
|
254
520
|
```ts
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
.then((data) => {
|
|
258
|
-
console.log(data) // а тут уже 'foo'
|
|
259
|
-
});
|
|
521
|
+
// инвалидация всех query с именем `doc`
|
|
522
|
+
docsFetcher.queries.doc.invalidate();
|
|
260
523
|
```
|
|
261
524
|
|
|
262
|
-
|
|
525
|
+
После вызова `invalidate` данные в кэше будут помечены как невалидные и при следующем обращении к query будет выполнен запрос на сервер. Если при вызове метода `invalidate` на изменения `.data` подписан любой store, то произойдет моментальный перезапрос активных query:
|
|
526
|
+
|
|
263
527
|
```ts
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
528
|
+
const docQuery = docsFetcher.queries.doc.create('docID');
|
|
529
|
+
|
|
530
|
+
await docQuery.async(); // данные записаны в кэш
|
|
531
|
+
|
|
532
|
+
docsFetcher.queries.doc.invalidate();
|
|
533
|
+
|
|
534
|
+
docQuery.data; // триггер запроса данных потому что они были помечены как невалидные
|
|
535
|
+
docQuery.isLoading; // true
|
|
270
536
|
```
|
|
271
537
|
|
|
272
|
-
|
|
538
|
+
#### Инвалидация всех query по их имени
|
|
273
539
|
|
|
540
|
+
Для инвалидации всех query по их имени можно вызвать метод `invalidate`:
|
|
274
541
|
```ts
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
() => {
|
|
278
|
-
console.log('cache-first request');
|
|
279
|
-
return Promise.resolve('foo');
|
|
280
|
-
},
|
|
281
|
-
{
|
|
282
|
-
fetchPolicy: 'cache-first',
|
|
283
|
-
}
|
|
284
|
-
);
|
|
542
|
+
const docQuery1 = docsFetcher.queries.doc.create('1');
|
|
543
|
+
const docQuery2 = docsFetcher.queries.doc.create('2');
|
|
285
544
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
() => {
|
|
289
|
-
console.log('network-only request');
|
|
290
|
-
return Promise.resolve('bar');
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
fetchPolicy: 'network-only',
|
|
294
|
-
}
|
|
295
|
-
);
|
|
545
|
+
docsFetcher.queries.doc.invalidate(); // данные docQuery1 и docQuery2 помечены как невалидные
|
|
546
|
+
```
|
|
296
547
|
|
|
297
|
-
|
|
298
|
-
await networkOnlyQuery.async(); // увидим консоль 'network-only request'
|
|
548
|
+
#### Инвалидация query по частичному совпадению параметров
|
|
299
549
|
|
|
300
|
-
|
|
301
|
-
|
|
550
|
+
Если необходимо инвалидировать данные по конкретным параметрам, то их необходимо передать в метод `invalidate`:
|
|
551
|
+
```ts
|
|
552
|
+
const docsFetcher = {
|
|
553
|
+
queries: {
|
|
554
|
+
doc: mobxQuery.createQuerySet((id: string) => ({
|
|
555
|
+
execute: () => docsEndpoint.getDoc(id).then(({ data }) => data),
|
|
556
|
+
})),
|
|
557
|
+
},
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
docsFetcher.queries.doc.invalidate('1'); // документ с id = 1 будет помечен как невалидный
|
|
561
|
+
```
|
|
302
562
|
|
|
303
|
-
|
|
304
|
-
['cache-first key'], // использован тот же самый ключ, что и для cacheFirstQuery
|
|
305
|
-
() => {
|
|
306
|
-
console.log('duplicate cache-first request');
|
|
307
|
-
return Promise.resolve('foo');
|
|
308
|
-
}
|
|
309
|
-
);
|
|
563
|
+
Параметры `invalidate` полностью совпадают с параметрами, описанными при определении query:
|
|
310
564
|
|
|
311
|
-
|
|
565
|
+
```ts
|
|
566
|
+
const docsFetcher = {
|
|
567
|
+
queries: {
|
|
568
|
+
doc: mobxQuery.createQuerySet((id: string, search: string, filters: Filters) => ({
|
|
569
|
+
execute: () => docsEndpoint.getDoc(id, search, filters),
|
|
570
|
+
})),
|
|
571
|
+
},
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
docsFetcher.queries.doc.invalidate('1', 'test', { sort: 'asc' });
|
|
312
575
|
```
|
|
313
|
-
**[Пример в sandbox](https://codesandbox.io/s/mobx-query-fetchpolicy-wvh8jl)**
|
|
314
|
-
|
|
315
|
-
# Вспомогательные флаги и поля
|
|
316
|
-
`Query`, `InfiniteQuery` и `Mutation` имеют одинаковый набор вспомогательных флагов и полей, работающих по единому принципу.
|
|
317
576
|
|
|
318
|
-
|
|
319
|
-
Boolean флаг, указывающий на процесс выполнения запроса
|
|
320
|
-
## isSuccess
|
|
321
|
-
Boolean флаг, указывающий на успешное выполнение запроса
|
|
322
|
-
## isError
|
|
323
|
-
Boolean флаг, указывающий на провалившийся запрос
|
|
324
|
-
## isIdle
|
|
325
|
-
Boolean флаг, указывающий на простаивание query, первый же вызов запроса переключит его в false
|
|
326
|
-
## error
|
|
327
|
-
Поле, содержащее информацию о последней ошибке
|
|
577
|
+
#### Инвалидация query по частичному совпадению параметра-объекта
|
|
328
578
|
|
|
329
579
|
```ts
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
() =>
|
|
333
|
-
)
|
|
580
|
+
const docsFetcher = {
|
|
581
|
+
queries: {
|
|
582
|
+
doc: mobxQuery.createQuerySet((id: string, filters: { sort: string; search: string }) => ({
|
|
583
|
+
execute: () => docsEndpoint.getDoc(id, filters),
|
|
584
|
+
})),
|
|
585
|
+
},
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
const query1 = docsFetcher.queries.doc.create('1', { sort: 'asc', search: 'test' });
|
|
589
|
+
const query2 = docsFetcher.queries.doc.create('1', { sort: 'desc', search: 'test' });
|
|
590
|
+
|
|
591
|
+
docsFetcher.queries.doc.invalidate('1', { sort: 'asc' }); // данные только query1 будут помечены как невалидные
|
|
592
|
+
```
|
|
334
593
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
594
|
+
### Кастомная установка ключей кэширования
|
|
595
|
+
|
|
596
|
+
При определении `QuerySet` и `InfiniteQuerySet` можно указать параметр `keys` и `name`, которые будут использоваться для формирования ключей кэша.
|
|
597
|
+
- `name` - название набора. Будет использоваться вместо хэша от функции конфигурации
|
|
598
|
+
- `keys` - будут использоваться вместо параметров функции конфигурации
|
|
340
599
|
|
|
341
|
-
|
|
342
|
-
|
|
600
|
+
```ts
|
|
601
|
+
const docsFetcher = {
|
|
602
|
+
queries: {
|
|
603
|
+
doc: mobxQuery.createQuerySet(
|
|
604
|
+
(id: string, search: string) => ({
|
|
605
|
+
keys: [id],
|
|
606
|
+
execute: () => docsEndpoint.getDoc(id, search),
|
|
607
|
+
}),
|
|
608
|
+
{ name: 'doc' }
|
|
609
|
+
),
|
|
610
|
+
},
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// ключ кэша будет равен "doc,1"
|
|
614
|
+
const query = docsFetcher.queries.doc.create('1', 'test');
|
|
615
|
+
|
|
616
|
+
docsFetcher.queries.doc.invalidate('1'); // данные query будут помечены как невалидные
|
|
617
|
+
docsFetcher.queries.doc.invalidate('1', 'test'); // второй параметр будет проигнорирован
|
|
343
618
|
```
|
|
344
619
|
|
|
345
|
-
##
|
|
346
|
-
`Query` и `InfiniteQuery` имеют режим фонового обновления. Предполагается, что будет хорошо подходить для обновления данных через websocket.
|
|
620
|
+
## Фоновая загрузка данных QuerySet и InfiniteQuerySet
|
|
347
621
|
|
|
348
|
-
|
|
622
|
+
Для фонового обновления данных возможно использовать флаг `isBackground` для query и infiniteQuery.
|
|
623
|
+
Данный метод хорошо подходит для инвалидации данных по событиям из WebSocket.
|
|
349
624
|
|
|
625
|
+
Пример для `QuerySet`:
|
|
350
626
|
```ts
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
{ isBackground: true }
|
|
355
|
-
);
|
|
627
|
+
const docQuery = docsFetcher.queries.doc.createWithConfig(({ isBackground: true }, 'docID');
|
|
628
|
+
|
|
629
|
+
docQuery.sync(); // запрос будет выполнен в фоновом режиме
|
|
356
630
|
|
|
357
|
-
|
|
358
|
-
console.log(query.isLoading); // переключался в true на момент запроса
|
|
359
|
-
console.log(query.isSuccess); // true
|
|
631
|
+
docQuery.isLoading; // true. Первый запрос изменит статусные флаги
|
|
360
632
|
|
|
361
|
-
|
|
362
|
-
await query.async();
|
|
363
|
-
console.log(query.isLoading); // не изменялся
|
|
364
|
-
console.log(query.isSuccess); // остался неизменным - true
|
|
633
|
+
await when(() => docQuery.isSuccess);
|
|
365
634
|
|
|
366
|
-
|
|
367
|
-
|
|
635
|
+
docsFetcher.queries.doc.invalidate();
|
|
636
|
+
|
|
637
|
+
docQuery.isLoading; // false. При этом запрос данных уже выполняется в фоновом режиме, если где-то есть подписчик на изменения `.data`
|
|
638
|
+
docQuery.background.isLoading; // true
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
## Изменение кэша QuerySet и InfiniteQuerySet
|
|
642
|
+
|
|
643
|
+
Для изменения данных в кэше необходимо использовать `forceUpdate`, содержащийся в query:
|
|
644
|
+
```ts
|
|
645
|
+
const docQuery = docsFetcher.queries.doc.create('docID');
|
|
646
|
+
|
|
647
|
+
// в кэше будет записано "{ name: 'test' }", а статустные флаги перейдут в состояние 'success'
|
|
648
|
+
docQuery.forceUpdate({ name: 'test' });
|
|
368
649
|
```
|
|
369
650
|
|
|
370
|
-
|
|
651
|
+
## Тестирование Fetcher на сонове QuerySet и InfiniteQuerySet.
|
|
652
|
+
|
|
653
|
+
Для мокинга QuerySet и InfiniteQuerySet необходимо использовать mock следующего вида:
|
|
654
|
+
```ts
|
|
655
|
+
type Fetcher = {
|
|
656
|
+
queries: QuerySet<any[], any>;
|
|
657
|
+
infiniteQueries: InfiniteQuerySet<any[], any>;
|
|
658
|
+
mutations: MutationSet<any[], any>;
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
const mockFetcher = <TFetcher extends Fetcher>(config: DeepPartial<TFetcher>) => config as TFetcher;
|
|
662
|
+
```
|
|
371
663
|
|
|
372
|
-
|
|
664
|
+
**Не используйте 'vitest-mock-extended' для мокинга Fetcher.**
|
|
665
|
+
Причина: `vitest-mock-extended` оборачивает объект в Proxy, что нарушает работу Mobx.
|
|
373
666
|
|
|
374
|
-
###
|
|
667
|
+
### Тестирование с включенным ```enabledAutoFetch```
|
|
375
668
|
|
|
376
669
|
```MobxQuery``` инициализируется с параметром: ```enabledAutoFetch```:
|
|
377
670
|
```ts
|
|
378
|
-
const createMobxQuery = () => new MobxQuery
|
|
671
|
+
const createMobxQuery = () => new MobxQuery({
|
|
379
672
|
enabledAutoFetch: true,
|
|
380
673
|
});
|
|
381
674
|
```
|
|
382
675
|
|
|
383
|
-
```
|
|
676
|
+
```booksFetcher.ts```
|
|
384
677
|
```ts
|
|
385
|
-
export
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}),
|
|
395
|
-
);
|
|
396
|
-
}
|
|
678
|
+
export const booksFetcher = {
|
|
679
|
+
queries: {
|
|
680
|
+
bookList: mobxQuery.createQuerySet((params: BooksEndpointsDTO.BookListInput) => ({
|
|
681
|
+
execute: () => booksEndpoint.getBookList(params),
|
|
682
|
+
})),
|
|
683
|
+
},
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
export type BooksFetcher = typeof booksFetcher;
|
|
397
687
|
```
|
|
398
688
|
|
|
399
|
-
```BooksListStore``` - использует
|
|
689
|
+
```BooksListStore``` - использует `BooksFetcher` для получения данных:
|
|
400
690
|
```ts
|
|
401
691
|
class BooksListStore {
|
|
402
692
|
public sort?: SortData;
|
|
403
693
|
|
|
404
|
-
constructor(private readonly
|
|
694
|
+
constructor(private readonly _booksFetcher: BooksFetcher) {
|
|
405
695
|
makeAutoObservable(this);
|
|
406
696
|
}
|
|
407
697
|
|
|
408
698
|
private get listQuery() {
|
|
409
|
-
return this.
|
|
699
|
+
return this._booksFetcher.queries.bookList.create(this.sort);
|
|
410
700
|
}
|
|
411
701
|
|
|
412
702
|
public get list(): ListItem[] {
|
|
413
|
-
const data = this.listQuery.data
|
|
703
|
+
const data = this.listQuery.data || [];
|
|
414
704
|
|
|
415
705
|
return data.map(({ id, name, price }) => ({
|
|
416
706
|
id,
|
|
@@ -421,34 +711,36 @@ class BooksListStore {
|
|
|
421
711
|
}
|
|
422
712
|
```
|
|
423
713
|
|
|
424
|
-
|
|
714
|
+
Тест ```BooksListStore```:
|
|
425
715
|
|
|
426
716
|
```ts
|
|
427
717
|
import { when } from 'mobx';
|
|
718
|
+
import { mockCacheGroups } from '@astral/mobx-query-vitest-mock';
|
|
428
719
|
|
|
429
720
|
describe('BooksListStore', () => {
|
|
430
721
|
it('Список книг форматируется для отображения', async () => {
|
|
431
722
|
// Для каждого теста необходимо инициализировать свой instance MobxQuery,
|
|
432
|
-
//
|
|
723
|
+
// в противном случае каждый тест будет модифицировать кэш
|
|
433
724
|
const mobxQuery = createMobxQuery();
|
|
434
725
|
|
|
435
726
|
const fakeBookList = makeFakeBookList(2, { price: 1000 });
|
|
436
727
|
const fakeBookListItem = fakeBookList.data[0];
|
|
437
728
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
729
|
+
const booksFetcherMock = mockFetcher<BooksFetcher>({
|
|
730
|
+
queries: {
|
|
731
|
+
bookList: () => mobxQuery.createQuerySet(() => ({
|
|
732
|
+
execute: async () => fakeBookList,
|
|
733
|
+
})),
|
|
734
|
+
},
|
|
443
735
|
});
|
|
444
736
|
|
|
445
|
-
const sut = new
|
|
737
|
+
const sut = new BooksListStore(booksFetcherMock);
|
|
446
738
|
|
|
447
739
|
// Ждем автоматической загрузки данных
|
|
448
740
|
// Загрузка данных начнется автоматически при обращении к sut.list за счет параметра enabledAutoFetch
|
|
449
741
|
await when(() => Boolean(sut.list?.length));
|
|
450
742
|
|
|
451
|
-
expect(sut.list[0]).
|
|
743
|
+
expect(sut.list[0]).toEqual({
|
|
452
744
|
id: fakeBookListItem.id,
|
|
453
745
|
name: fakeBookListItem.name,
|
|
454
746
|
price: '1 000 руб.',
|
|
@@ -457,3 +749,472 @@ describe('BooksListStore', () => {
|
|
|
457
749
|
});
|
|
458
750
|
```
|
|
459
751
|
|
|
752
|
+
## Core
|
|
753
|
+
|
|
754
|
+
Концепции, описанные ниже являются ядром библиотеки и используются внутри `QuerySet`, `InfiniteQuerySet` и `MutationSet`.
|
|
755
|
+
|
|
756
|
+
### Query
|
|
757
|
+
|
|
758
|
+
`Query` позволяет получать данные из API и кешировать их. Query не должны производить изменения.
|
|
759
|
+
|
|
760
|
+
#### Создание Query
|
|
761
|
+
|
|
762
|
+
В примере ниже будет создан doc query, который будет получать данные по id документа. После успешного выполнения запроса данные будут закэшированы.
|
|
763
|
+
|
|
764
|
+
```ts
|
|
765
|
+
const createDocQuery = (id: string) =>
|
|
766
|
+
// первый параметр - ключи, по которому в кэше будет храниться ответ запроса
|
|
767
|
+
cacheService.createQuery(['doc', id],
|
|
768
|
+
(params: { id: string }) => docsEndpoint.getDoc(params.id)
|
|
769
|
+
);
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
#### Использование Query
|
|
773
|
+
|
|
774
|
+
```ts
|
|
775
|
+
const createDocQuery = (id: string) =>
|
|
776
|
+
// первый параметр - ключи, по которому в кэше будет храниться ответ запроса
|
|
777
|
+
cacheService.createQuery(['doc', id],
|
|
778
|
+
(params: { id: string }) => docsEndpoint.getDoc(params.id)
|
|
779
|
+
);
|
|
780
|
+
|
|
781
|
+
const docQuery = createDocQuery('docID');
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
`docQuery` является объектом, который содержит всю информацию по запросу и методы работы с запросом:
|
|
785
|
+
|
|
786
|
+
#### Interface Query
|
|
787
|
+
|
|
788
|
+
```ts
|
|
789
|
+
export type Query<TResult = unknown, TError = unknown, TIsBackground = boolean> = {
|
|
790
|
+
/**
|
|
791
|
+
* Текущие данные запроса
|
|
792
|
+
*/
|
|
793
|
+
data: TResult | undefined;
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Флаг загрузки
|
|
797
|
+
*/
|
|
798
|
+
isLoading: boolean;
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Флаг успешного выполнения запроса
|
|
802
|
+
*/
|
|
803
|
+
isSuccess: boolean;
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Флаг наличия ошибки
|
|
807
|
+
*/
|
|
808
|
+
isError: boolean;
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Текущая ошибка
|
|
812
|
+
*/
|
|
813
|
+
error: TError | null;
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Флаг, обозначающий простаивание, т.е. запроса еще не было
|
|
817
|
+
*/
|
|
818
|
+
isIdle: boolean;
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Синхронизирует данные с сервером
|
|
822
|
+
* @param onSuccess - Callback успешного выполнения
|
|
823
|
+
* @param onError - Callback ошибки
|
|
824
|
+
*/
|
|
825
|
+
sync: ({ onSuccess, onError }: SyncParams<TResult, TError>) => void;
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Асинхронный метод получения данных
|
|
829
|
+
*/
|
|
830
|
+
async: () => Promise<TResult>;
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Метод инвалидации текущего query
|
|
834
|
+
*/
|
|
835
|
+
invalidate: () => void;
|
|
836
|
+
|
|
837
|
+
// Статусы, изменяющиеся после первого успешного запроса в режиме фоновой загрузки isBackground: true
|
|
838
|
+
background: {
|
|
839
|
+
/**
|
|
840
|
+
* Флаг обозначающий загрузку данных в фоновом режиме
|
|
841
|
+
*/
|
|
842
|
+
isLoading: boolean;
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Флаг обозначающий, что последний запрос был зафейлен в фоновом режиме
|
|
846
|
+
*/
|
|
847
|
+
isError: boolean;
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Данные о последней ошибке в фоновом режиме
|
|
851
|
+
*/
|
|
852
|
+
error?: TError;
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Флаг, обозначающий успешность завершения последнего запроса в фоновом режиме
|
|
856
|
+
*/
|
|
857
|
+
isSuccess: boolean;
|
|
858
|
+
};
|
|
859
|
+
};
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
#### Синхронный вызов запроса данных
|
|
863
|
+
|
|
864
|
+
Метод `sync` позволяет синхронно запустить запрос на получение данных:
|
|
865
|
+
```ts
|
|
866
|
+
const createDocQuery = (id: string) =>
|
|
867
|
+
// первый параметр - ключи, по которому в кэше будет храниться ответ запроса
|
|
868
|
+
cacheService.createQuery(['doc', id],
|
|
869
|
+
(params: { id: string }) => docsEndpoint.getDoc(params.id)
|
|
870
|
+
);
|
|
871
|
+
|
|
872
|
+
const docQuery = createDocQuery('docID');
|
|
873
|
+
|
|
874
|
+
// есть callbacks на обработку success и error
|
|
875
|
+
docQuery.sync({
|
|
876
|
+
onSuccess: (data) => {
|
|
877
|
+
console.log(data);
|
|
878
|
+
},
|
|
879
|
+
onError: (error) => {
|
|
880
|
+
console.log(error);
|
|
881
|
+
},
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
// true
|
|
885
|
+
docQuery.isLoading;
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
#### Асинхронный вызов запроса данных
|
|
889
|
+
|
|
890
|
+
Метод `async` позволяет асинхронно запустить запрос на получение данных:
|
|
891
|
+
```ts
|
|
892
|
+
const createDocQuery = (id: string) =>
|
|
893
|
+
// первый параметр - ключи, по которому в кэше будет храниться ответ запроса
|
|
894
|
+
cacheService.createQuery(['doc', id],
|
|
895
|
+
(params: { id: string }) => docsEndpoint.getDoc(params.id)
|
|
896
|
+
);
|
|
897
|
+
|
|
898
|
+
const docQuery = createDocQuery('docID');
|
|
899
|
+
|
|
900
|
+
// передавать параметры запроса не нужно потому что они уже были переданы при вызове init
|
|
901
|
+
const response = await docQuery.async();
|
|
902
|
+
|
|
903
|
+
docQuery.isSuccess; // true
|
|
904
|
+
docQuery.data; // идентичен response
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
#### Автоматический запрос данных
|
|
908
|
+
|
|
909
|
+
Если MobxQuery был создан с флагом `enableAutoFetch: true`, то данные будут автоматически запрошены при обращении к полю `data`:
|
|
910
|
+
```ts
|
|
911
|
+
const cacheService = new MobxQuery({ enableAutoFetch: true });
|
|
912
|
+
|
|
913
|
+
const createDocQuery = (id: string) =>
|
|
914
|
+
// первый параметр - ключи, по которому в кэше будет храниться ответ запроса
|
|
915
|
+
cacheService.createQuery(['doc', id],
|
|
916
|
+
(params: { id: string }) => docsEndpoint.getDoc(params.id)
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
docQuery.data; // триггер запроса данных
|
|
920
|
+
|
|
921
|
+
docQuery.isLoading; // true
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
Если MobxQuery был создан с флагом `enableAutoFetch: false`, то автоматически запрос данных можно включить для текущего query:
|
|
925
|
+
```ts
|
|
926
|
+
const createDocQuery = (id: string) =>
|
|
927
|
+
cacheService.createQuery(['doc', id],
|
|
928
|
+
(params: { id: string }) => docsEndpoint.getDoc(params.id),
|
|
929
|
+
{ enableAutoFetch: true }
|
|
930
|
+
);
|
|
931
|
+
|
|
932
|
+
docQuery.data; // триггер запроса данных
|
|
933
|
+
|
|
934
|
+
docQuery.isLoading; // true
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
### InfiniteQuery
|
|
938
|
+
|
|
939
|
+
`InfiniteQuery` предназначен для получения бесконечного списка данных.
|
|
940
|
+
|
|
941
|
+
### Создание InfiniteQuery
|
|
942
|
+
|
|
943
|
+
В примере ниже будет создан docList InfiniteQuery. После успешного выполнения запроса данные будут закэшированы.
|
|
944
|
+
|
|
945
|
+
```ts
|
|
946
|
+
const createDocListInfiniteQuery = (filters: Omit<DocsEndpointsDTO.DocListFilters, 'offset' | 'count'>) =>
|
|
947
|
+
cacheService.createInfiniteQuery(['docList', filters],
|
|
948
|
+
(params: { offset: number; count: number }) => docsEndpoint.getDocList({ offset: params.offset, count: params.count, ...filters })
|
|
949
|
+
);
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
#### Использование InfiniteQuery
|
|
953
|
+
|
|
954
|
+
```ts
|
|
955
|
+
const createDocListInfiniteQuery = (filters: Omit<DocsEndpointsDTO.DocListFilters, 'offset' | 'count'>) =>
|
|
956
|
+
cacheService.createInfiniteQuery(['docList', filters],
|
|
957
|
+
(params: { offset: number; count: number }) => docsEndpoint.getDocList({ offset: params.offset, count: params.count, ...filters })
|
|
958
|
+
);
|
|
959
|
+
|
|
960
|
+
// первым параметром create является - filters: Omit<DocsEndpointsDTO.DocListFilters, 'offset' | 'count'>
|
|
961
|
+
const docListQuery = createDocListInfiniteQuery({ search: 'test' });
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
#### Загрузка данных
|
|
965
|
+
|
|
966
|
+
```ts
|
|
967
|
+
import { when } from 'mobx';
|
|
968
|
+
|
|
969
|
+
const createDocListInfiniteQuery = (filters: Omit<DocsEndpointsDTO.DocListFilters, 'offset' | 'count'>) =>
|
|
970
|
+
cacheService.createInfiniteQuery(['docList', filters],
|
|
971
|
+
(params: { offset: number; count: number }) => docsEndpoint.getDocList({ offset: params.offset, count: params.count, ...filters })
|
|
972
|
+
);
|
|
973
|
+
|
|
974
|
+
const docListQuery = createDocListInfiniteQuery({ search: 'test' });
|
|
975
|
+
|
|
976
|
+
await docListQuery.async(); // запрос на получение первых 30 записей
|
|
977
|
+
|
|
978
|
+
docListQuery.data.length; // 30 записей
|
|
979
|
+
|
|
980
|
+
docListQuery.fetchMore(); // запрос на получение следующих 30 записей
|
|
981
|
+
|
|
982
|
+
// ждем загрузки
|
|
983
|
+
await when(() => !query.isLoading);
|
|
984
|
+
|
|
985
|
+
docListQuery.data.length; // 60 записей
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
#### `isEndReached`. Определение конца списка
|
|
989
|
+
|
|
990
|
+
Флаг `isEndReached` будет установлен в `true`, если записи для загрузки закончились:
|
|
991
|
+
```ts
|
|
992
|
+
import { when } from 'mobx';
|
|
993
|
+
|
|
994
|
+
const createDocListInfiniteQuery = (filters: Omit<DocsEndpointsDTO.DocListFilters, 'offset' | 'count'>) =>
|
|
995
|
+
cacheService.createInfiniteQuery(['docList', filters],
|
|
996
|
+
(params: { offset: number; count: number }) => docsEndpoint.getDocList({ offset: params.offset, count: params.count, ...filters })
|
|
997
|
+
);
|
|
998
|
+
|
|
999
|
+
const docListQuery = createDocListInfiniteQuery({ search: 'test' });
|
|
1000
|
+
|
|
1001
|
+
await docListQuery.async(); // запрос на получение первых 30 записей
|
|
1002
|
+
|
|
1003
|
+
docListQuery.data.length; // 30 записей
|
|
1004
|
+
|
|
1005
|
+
docListQuery.fetchMore(); // запрос на получение следующих 30 записей
|
|
1006
|
+
|
|
1007
|
+
// ждем загрузки
|
|
1008
|
+
await when(() => !query.isLoading);
|
|
1009
|
+
|
|
1010
|
+
docListQuery.data.length; // 19 записей. Последняя страница содержит 9 записей, а не 30, как было запрошено, значит больше данных нет
|
|
1011
|
+
|
|
1012
|
+
docListQuery.isEndReached; // true
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
Флаг `isEndReached` устанавливается в `true`, когда количество полученных элементов меньше запрошенного количества, что означает отсутствие дополнительных данных на сервере.
|
|
1016
|
+
|
|
1017
|
+
#### Изменение количества запрашиваемых записей
|
|
1018
|
+
|
|
1019
|
+
Для изменения конфигурации InfiniteQuery необходимо использовать третий параметр `config`:
|
|
1020
|
+
```ts
|
|
1021
|
+
const createDocListInfiniteQuery = (filters: Omit<DocsEndpointsDTO.DocListFilters, 'offset' | 'count'>) =>
|
|
1022
|
+
cacheService.createInfiniteQuery(['docList', filters],
|
|
1023
|
+
(params: { offset: number; count: number }) => docsEndpoint.getDocList({ offset: params.offset, count: params.count, ...filters }),
|
|
1024
|
+
{ incrementCount: 10 }
|
|
1025
|
+
);
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
#### Интерфейс InfiniteQueryConfig
|
|
1029
|
+
```ts
|
|
1030
|
+
export type InfiniteQueryConfig = {
|
|
1031
|
+
/**
|
|
1032
|
+
* Количество записей, которое будет загружено при первом и следующих запросах
|
|
1033
|
+
* @default 30
|
|
1034
|
+
*/
|
|
1035
|
+
incrementCount?: number;
|
|
1036
|
+
/**
|
|
1037
|
+
* Обработчик ошибки
|
|
1038
|
+
*/
|
|
1039
|
+
onError?: (error: unknown) => void;
|
|
1040
|
+
/**
|
|
1041
|
+
* Флаг, отвечающий за автоматический запрос данных при обращении к полю data
|
|
1042
|
+
*/
|
|
1043
|
+
enabledAutoFetch?: boolean;
|
|
1044
|
+
/**
|
|
1045
|
+
* Политика кэширования данных.
|
|
1046
|
+
*/
|
|
1047
|
+
fetchPolicy?: FetchPolicy;
|
|
1048
|
+
/**
|
|
1049
|
+
* Режим фонового обновления
|
|
1050
|
+
* @default false
|
|
1051
|
+
*/
|
|
1052
|
+
isBackground?: boolean;
|
|
1053
|
+
};
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
### Mutation
|
|
1057
|
+
|
|
1058
|
+
`Mutation` предназначен для отправки данных на сервер с целью произведения изменений.
|
|
1059
|
+
|
|
1060
|
+
### Создание и использование mutations
|
|
1061
|
+
|
|
1062
|
+
В примере ниже будет создан `editDoc` mutation, который отправляет данные на сервер для редактирования документа:
|
|
1063
|
+
```ts
|
|
1064
|
+
const createEditDocMutation = (params: DocsEndpointsDTO.EditDocInput) =>
|
|
1065
|
+
cacheService.createMutation((params: DocsEndpointsDTO.EditDocInput) => docsEndpoint.editDoc(params));
|
|
1066
|
+
|
|
1067
|
+
const editDocMutation = createEditDocMutation();
|
|
1068
|
+
|
|
1069
|
+
// params являются параметрами, указанными при определении mutation - DocsEndpointsDTO.EditDocInput
|
|
1070
|
+
await editDocMutation.async({ params: { id: 'docID', name: 'test' } });
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
#### Интерфейс Mutation
|
|
1074
|
+
|
|
1075
|
+
```ts
|
|
1076
|
+
type Mutation<TResult, TError = unknown, TExecutorParams = void> = {
|
|
1077
|
+
/**
|
|
1078
|
+
* Синхронный метод выполнения мутации
|
|
1079
|
+
*/
|
|
1080
|
+
sync: (options: {
|
|
1081
|
+
onSuccess?: (res: TResult) => void;
|
|
1082
|
+
onError?: (e: TError) => void;
|
|
1083
|
+
params?: TExecutorParams;
|
|
1084
|
+
}) => void;
|
|
1085
|
+
|
|
1086
|
+
/**
|
|
1087
|
+
* Асинхронный метод выполнения мутации
|
|
1088
|
+
*/
|
|
1089
|
+
async: (params: TExecutorParams) => Promise<TResult>;
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* Флаг загрузки
|
|
1093
|
+
*/
|
|
1094
|
+
isLoading: boolean;
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* Флаг успешного выполнения запроса
|
|
1098
|
+
*/
|
|
1099
|
+
isSuccess: boolean;
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* Флаг наличия ошибки
|
|
1103
|
+
*/
|
|
1104
|
+
isError: boolean;
|
|
1105
|
+
|
|
1106
|
+
/**
|
|
1107
|
+
* Текущая ошибка
|
|
1108
|
+
*/
|
|
1109
|
+
error: TError | null;
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Флаг, обозначающий простаивание, т.е. запроса еще не было
|
|
1113
|
+
*/
|
|
1114
|
+
isIdle: boolean;
|
|
1115
|
+
|
|
1116
|
+
// Статусы, изменяющиеся после первого успешного запроса в режиме фоновой загрузки isBackground: true
|
|
1117
|
+
background: {
|
|
1118
|
+
/**
|
|
1119
|
+
* Флаг обозначающий загрузку данных в фоновом режиме
|
|
1120
|
+
*/
|
|
1121
|
+
isLoading: boolean;
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Флаг обозначающий, что последний запрос был зафейлен в фоновом режиме
|
|
1125
|
+
*/
|
|
1126
|
+
isError: boolean;
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* Данные о последней ошибке в фоновом режиме
|
|
1130
|
+
*/
|
|
1131
|
+
error?: TError;
|
|
1132
|
+
|
|
1133
|
+
/**
|
|
1134
|
+
* Флаг, обозначающий успешность завершения последнего запроса в фоновом режиме
|
|
1135
|
+
*/
|
|
1136
|
+
isSuccess: boolean;
|
|
1137
|
+
};
|
|
1138
|
+
};
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
#### Синхронный вызов mutation
|
|
1142
|
+
|
|
1143
|
+
Метод `sync` позволяет синхронно запустить запрос:
|
|
1144
|
+
```ts
|
|
1145
|
+
const createEditDocMutation = (params: DocsEndpointsDTO.EditDocInput) =>
|
|
1146
|
+
cacheService.createMutation((params: DocsEndpointsDTO.EditDocInput) => docsEndpoint.editDoc(params));
|
|
1147
|
+
|
|
1148
|
+
const editDocMutation = createEditDocMutation();
|
|
1149
|
+
|
|
1150
|
+
// есть callbacks на обработку success и error
|
|
1151
|
+
editDocMutation.sync({
|
|
1152
|
+
params: { id: 'docID', name: 'test' },
|
|
1153
|
+
onSuccess: (data) => {
|
|
1154
|
+
console.log(data);
|
|
1155
|
+
},
|
|
1156
|
+
onError: (error) => {
|
|
1157
|
+
console.log(error);
|
|
1158
|
+
},
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
// true
|
|
1162
|
+
editDocMutation.isLoading;
|
|
1163
|
+
```
|
|
1164
|
+
|
|
1165
|
+
##### Асинхронный вызов mutation
|
|
1166
|
+
|
|
1167
|
+
Метод `async` позволяет асинхронно запустить запрос:
|
|
1168
|
+
```ts
|
|
1169
|
+
const createEditDocMutation = (params: DocsEndpointsDTO.EditDocInput) =>
|
|
1170
|
+
cacheService.createMutation((params: DocsEndpointsDTO.EditDocInput) => docsEndpoint.editDoc(params));
|
|
1171
|
+
|
|
1172
|
+
const editDocMutation = createEditDocMutation();
|
|
1173
|
+
|
|
1174
|
+
await editDocMutation.async({ params: { id: 'docID', name: 'test' } });
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
### Особенности инвалидации Queries
|
|
1178
|
+
|
|
1179
|
+
Как при создании query, так и при инвалидации, нужно использовать массив ключей. Предполагается, что query может быть инвалидирован по нескольким ключам.
|
|
1180
|
+
|
|
1181
|
+
```ts
|
|
1182
|
+
const query = mobxQuery.createQuery(
|
|
1183
|
+
['key one', 'key two'], // ключ - массив строк
|
|
1184
|
+
() => Promise.resolve('foo'),
|
|
1185
|
+
{ enabledAutoFetch: true }
|
|
1186
|
+
);
|
|
1187
|
+
|
|
1188
|
+
mobxQuery.invalidate(['key two']); // query будет инвалидирован
|
|
1189
|
+
mobxQuery.invalidate(['key one']); // query будет инвалидирован
|
|
1190
|
+
```
|
|
1191
|
+
|
|
1192
|
+
Но, стоит учитывать, что ключом является цельный элемент массива, а не составляющие элемента.
|
|
1193
|
+
```ts
|
|
1194
|
+
const query = mobxQuery.createQuery(
|
|
1195
|
+
[['key one', 'key two']], // ключ - двумерный массив строк
|
|
1196
|
+
() => Promise.resolve('foo'),
|
|
1197
|
+
{ enabledAutoFetch: true }
|
|
1198
|
+
);
|
|
1199
|
+
|
|
1200
|
+
mobxQuery.invalidate(['key one']); // ключ не совпадает, query НЕ будет инвалидирован
|
|
1201
|
+
```
|
|
1202
|
+
|
|
1203
|
+
Инвалидация будет происходить только для query, поле data которых считывается в данный момент. Для query, data которых будут отрендерены позже, запрос произойдет только в момент использования. Для превентивного обновления данных потребуется последовательное использование sync/async методов сразу после invalidate.
|
|
1204
|
+
|
|
1205
|
+
### Массовая инвалидация
|
|
1206
|
+
|
|
1207
|
+
Для инвалидации всех query необходимо использовать метод `invalidateQueries`:
|
|
1208
|
+
```ts
|
|
1209
|
+
mobxQuery.invalidateQueries();
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
### Изменение кэша
|
|
1213
|
+
|
|
1214
|
+
Для изменения данных в кэше необходимо использовать `forceUpdate`, содержащийся в query:
|
|
1215
|
+
```ts
|
|
1216
|
+
const query = mobxQuery.createQuery(['key'], () => Promise.resolve('1'));
|
|
1217
|
+
|
|
1218
|
+
// в кэше будет записано '2', а статустные флаги перейдут в состояние 'success'
|
|
1219
|
+
query.forceUpdate('2');
|
|
1220
|
+
```
|