@dxtmisha/wiki 0.24.0 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,517 @@
1
+ import {Meta} from '@storybook/addon-docs/blocks'
2
+
3
+ <Meta title='@dxtmisha/functional/ru/Composables/useApiRef'/>
4
+
5
+ # Композабл useApiRef
6
+
7
+ Композабл для работы с HTTP-запросами в Vue 3 приложениях. Предоставляет реактивный интерфейс для выполнения API запросов с автоматическим управлением состоянием загрузки, поддержкой реактивности параметров, условным выполнением и трансформацией данных.
8
+
9
+ ## Основные возможности
10
+
11
+ - **Реактивные запросы** — автоматическое выполнение запросов при изменении параметров
12
+ - **Управление состоянием** — автоматическое отслеживание состояния загрузки и готовности данных
13
+ - **Условное выполнение** — возможность выполнять запросы только при выполнении условий
14
+ - **Трансформация данных** — преобразование полученных данных перед сохранением
15
+ - **Интеграция с Api** — использует класс Api для выполнения запросов с поддержкой кеширования
16
+ - **Глобальные условия** — возможность установить глобальные условия для всех useApiRef
17
+ - **Автоочистка** — опциональная очистка данных при размонтировании компонента
18
+ - **TypeScript поддержка** — полная типизация с дженериками
19
+
20
+ ## Базовое использование
21
+
22
+ ### Простой GET запрос
23
+
24
+ ```javascript
25
+ import { useApiRef } from '@dxtmisha/functional'
26
+
27
+ // Выполняет GET запрос при монтировании компонента
28
+ const { data, loading, isStarting, reading } = useApiRef('/api/users')
29
+
30
+ // data - ref с данными ответа
31
+ // loading - ref с состоянием загрузки
32
+ // isStarting - computed, true до первой загрузки данных
33
+ // reading - computed, true во время чтения данных
34
+ ```
35
+
36
+ ### Использование в компоненте
37
+
38
+ ```vue
39
+ <script setup>
40
+ import { useApiRef } from '@dxtmisha/functional'
41
+
42
+ const { data: users, loading } = useApiRef('/api/users')
43
+ </script>
44
+
45
+ <template>
46
+ <div v-if="loading">Загрузка...</div>
47
+ <div v-else-if="users">
48
+ <div v-for="user in users" :key="user.id">
49
+ {{ user.name }}
50
+ </div>
51
+ </div>
52
+ </template>
53
+ ```
54
+
55
+ ## Параметры
56
+
57
+ ### `path`
58
+
59
+ Путь к API endpoint. Может быть строкой или ref-ом для реактивности.
60
+
61
+ **Тип:** `RefOrNormal<string | undefined>`
62
+
63
+ ```javascript
64
+ // Статический путь
65
+ const { data } = useApiRef('/api/users')
66
+
67
+ // Реактивный путь
68
+ const userId = ref(1)
69
+ const { data: user } = useApiRef(computed(() => `/api/users/${userId.value}`))
70
+
71
+ // При изменении userId автоматически выполняется новый запрос
72
+ userId.value = 2
73
+ ```
74
+
75
+ ### `options`
76
+
77
+ Параметры запроса. Может быть HTTP методом, объектом настроек или ref-ом.
78
+
79
+ **Тип:** `ApiMethodItem | RefOrNormal<ApiFetch>`
80
+
81
+ ```javascript
82
+ // Указать только метод
83
+ const { data } = useApiRef('/api/users', 'POST')
84
+
85
+ // Полные настройки
86
+ const { data } = useApiRef('/api/users', {
87
+ method: 'POST',
88
+ request: { name: 'Иван', email: 'ivan@example.com' },
89
+ headers: { 'Content-Type': 'application/json' }
90
+ })
91
+
92
+ // Реактивные параметры
93
+ const params = ref({ page: 1, limit: 10 })
94
+ const { data } = useApiRef('/api/users', computed(() => ({
95
+ method: 'GET',
96
+ request: params.value
97
+ })))
98
+
99
+ // При изменении params автоматически выполняется новый запрос
100
+ params.value = { page: 2, limit: 10 }
101
+ ```
102
+
103
+ ### `reactivity`
104
+
105
+ Включить или отключить автоматическое выполнение при изменении параметров.
106
+
107
+ **Тип:** `boolean`
108
+ **По умолчанию:** `true`
109
+
110
+ ```javascript
111
+ // С реактивностью (по умолчанию)
112
+ const params = ref({ page: 1 })
113
+ const { data, reset } = useApiRef('/api/users', { request: params }, true)
114
+
115
+ params.value.page = 2 // Автоматически выполнится запрос
116
+
117
+ // Без реактивности
118
+ const { data, reset } = useApiRef('/api/users', { request: params }, false)
119
+
120
+ params.value.page = 2 // Запрос НЕ выполнится
121
+ await reset() // Нужно вызвать reset вручную
122
+ ```
123
+
124
+ ### `conditions`
125
+
126
+ Условие для выполнения запроса. Запрос выполняется только если условие истинно.
127
+
128
+ **Тип:** `RefType<boolean>`
129
+
130
+ ```javascript
131
+ // Запрос выполняется только когда isAuth === true
132
+ const isAuth = ref(false)
133
+ const { data } = useApiRef('/api/profile', undefined, true, isAuth)
134
+
135
+ // data будет undefined, пока isAuth === false
136
+ console.log(data.value) // undefined
137
+
138
+ isAuth.value = true // Запрос автоматически выполнится
139
+ ```
140
+
141
+ ### `transformation`
142
+
143
+ Функция для преобразования полученных данных перед сохранением в ref.
144
+
145
+ **Тип:** `(data: T) => R`
146
+
147
+ ```javascript
148
+ // Преобразование данных API в нужный формат
149
+ const { data } = useApiRef(
150
+ '/api/users',
151
+ undefined,
152
+ true,
153
+ undefined,
154
+ (response) => {
155
+ // response - данные от API
156
+ // Возвращаем преобразованные данные
157
+ return response.data.map(user => ({
158
+ id: user.id,
159
+ fullName: `${user.firstName} ${user.lastName}`,
160
+ isActive: user.status === 'active'
161
+ }))
162
+ }
163
+ )
164
+
165
+ // data содержит преобразованные данные
166
+ ```
167
+
168
+ ### `unmounted`
169
+
170
+ Очищать ли данные при размонтировании компонента.
171
+
172
+ **Тип:** `boolean`
173
+ **По умолчанию:** `undefined`
174
+
175
+ ```javascript
176
+ // Данные будут очищены при размонтировании компонента
177
+ const { data } = useApiRef('/api/users', undefined, true, undefined, undefined, true)
178
+
179
+ // При переходе на другую страницу data станет undefined
180
+ ```
181
+
182
+ ## Возвращаемые значения
183
+
184
+ ### `data`
185
+
186
+ Ref с данными ответа. `undefined` до первой загрузки.
187
+
188
+ **Тип:** `Ref<R | undefined>`
189
+
190
+ ```javascript
191
+ const { data } = useApiRef('/api/users')
192
+
193
+ console.log(data.value) // undefined до загрузки
194
+ // После загрузки: массив пользователей
195
+ ```
196
+
197
+ ### `loading`
198
+
199
+ Ref с состоянием загрузки. `true` во время выполнения запроса.
200
+
201
+ **Тип:** `Ref<boolean>`
202
+
203
+ ```javascript
204
+ const { loading } = useApiRef('/api/users')
205
+
206
+ console.log(loading.value) // true во время загрузки, false после
207
+ ```
208
+
209
+ ### `isStarting`
210
+
211
+ Computed, показывает, загружены ли данные хотя бы раз. `true` до первой загрузки.
212
+
213
+ **Тип:** `ComputedRef<boolean>`
214
+
215
+ ```javascript
216
+ const { data, isStarting } = useApiRef('/api/users')
217
+
218
+ console.log(isStarting.value) // true до первой загрузки
219
+ // После первой загрузки всегда false, даже при повторных запросах
220
+ ```
221
+
222
+ ### `reading`
223
+
224
+ Computed, показывает активное чтение данных.
225
+
226
+ **Тип:** `ComputedRef<boolean>`
227
+
228
+ ```javascript
229
+ const { reading } = useApiRef('/api/users')
230
+
231
+ console.log(reading.value) // true во время чтения данных
232
+ ```
233
+
234
+ ### `reset`
235
+
236
+ Функция для принудительного выполнения запроса.
237
+
238
+ **Тип:** `() => Promise<void>`
239
+
240
+ ```javascript
241
+ const { data, reset } = useApiRef('/api/users')
242
+
243
+ // Принудительно перезагрузить данные
244
+ await reset()
245
+ ```
246
+
247
+ ## Примеры использования
248
+
249
+ ### Список с фильтрами
250
+
251
+ ```vue
252
+ <script setup>
253
+ import { ref, computed } from 'vue'
254
+ import { useApiRef } from '@dxtmisha/functional'
255
+
256
+ const searchQuery = ref('')
257
+ const currentPage = ref(1)
258
+
259
+ const { data: users, loading } = useApiRef(
260
+ '/api/users',
261
+ computed(() => ({
262
+ method: 'GET',
263
+ request: {
264
+ search: searchQuery.value,
265
+ page: currentPage.value,
266
+ limit: 20
267
+ }
268
+ }))
269
+ )
270
+
271
+ const handleSearch = (query) => {
272
+ searchQuery.value = query
273
+ currentPage.value = 1 // Сброс на первую страницу
274
+ // Запрос выполнится автоматически
275
+ }
276
+ </script>
277
+
278
+ <template>
279
+ <div>
280
+ <input v-model="searchQuery" placeholder="Поиск...">
281
+ <div v-if="loading">Загрузка...</div>
282
+ <div v-else>
283
+ <div v-for="user in users" :key="user.id">{{ user.name }}</div>
284
+ <button @click="currentPage++">Следующая страница</button>
285
+ </div>
286
+ </div>
287
+ </template>
288
+ ```
289
+
290
+ ### Условная загрузка
291
+
292
+ ```vue
293
+ <script setup>
294
+ import { ref } from 'vue'
295
+ import { useApiRef } from '@dxtmisha/functional'
296
+
297
+ const showDetails = ref(false)
298
+ const userId = ref(1)
299
+
300
+ // Данные загружаются только когда showDetails === true
301
+ const { data: userDetails, loading } = useApiRef(
302
+ computed(() => `/api/users/${userId.value}/details`),
303
+ undefined,
304
+ true,
305
+ showDetails
306
+ )
307
+
308
+ const toggleDetails = () => {
309
+ showDetails.value = !showDetails.value
310
+ // Если showDetails стало true, запрос выполнится автоматически
311
+ }
312
+ </script>
313
+
314
+ <template>
315
+ <button @click="toggleDetails">
316
+ {{ showDetails ? 'Скрыть' : 'Показать' }} детали
317
+ </button>
318
+ <div v-if="showDetails">
319
+ <div v-if="loading">Загрузка деталей...</div>
320
+ <div v-else-if="userDetails">{{ userDetails }}</div>
321
+ </div>
322
+ </template>
323
+ ```
324
+
325
+ ### POST запрос с трансформацией
326
+
327
+ ```vue
328
+ <script setup>
329
+ import { ref } from 'vue'
330
+ import { useApiRef } from '@dxtmisha/functional'
331
+
332
+ const formData = ref({
333
+ firstName: '',
334
+ lastName: '',
335
+ email: ''
336
+ })
337
+
338
+ const { data: createdUser, loading, reset } = useApiRef(
339
+ '/api/users',
340
+ {
341
+ method: 'POST',
342
+ request: formData
343
+ },
344
+ false, // Отключаем автоматическое выполнение
345
+ undefined,
346
+ (response) => {
347
+ // Преобразуем ответ API
348
+ return {
349
+ id: response.id,
350
+ fullName: `${response.firstName} ${response.lastName}`,
351
+ email: response.email,
352
+ createdAt: new Date(response.created_at)
353
+ }
354
+ }
355
+ )
356
+
357
+ const handleSubmit = async () => {
358
+ await reset() // Выполняем запрос вручную
359
+ if (createdUser.value) {
360
+ console.log('Пользователь создан:', createdUser.value)
361
+ }
362
+ }
363
+ </script>
364
+
365
+ <template>
366
+ <form @submit.prevent="handleSubmit">
367
+ <input v-model="formData.firstName" placeholder="Имя">
368
+ <input v-model="formData.lastName" placeholder="Фамилия">
369
+ <input v-model="formData.email" placeholder="Email">
370
+ <button :disabled="loading">
371
+ {{ loading ? 'Отправка...' : 'Создать' }}
372
+ </button>
373
+ </form>
374
+ </template>
375
+ ```
376
+
377
+ ### Множественные запросы
378
+
379
+ ```vue
380
+ <script setup>
381
+ import { useApiRef } from '@dxtmisha/functional'
382
+
383
+ const { data: users, loading: usersLoading } = useApiRef('/api/users')
384
+ const { data: posts, loading: postsLoading } = useApiRef('/api/posts')
385
+ const { data: comments, loading: commentsLoading } = useApiRef('/api/comments')
386
+
387
+ const isLoading = computed(() =>
388
+ usersLoading.value || postsLoading.value || commentsLoading.value
389
+ )
390
+ </script>
391
+
392
+ <template>
393
+ <div v-if="isLoading">Загрузка данных...</div>
394
+ <div v-else>
395
+ <section>
396
+ <h2>Пользователи</h2>
397
+ <div v-for="user in users" :key="user.id">{{ user.name }}</div>
398
+ </section>
399
+ <section>
400
+ <h2>Посты</h2>
401
+ <div v-for="post in posts" :key="post.id">{{ post.title }}</div>
402
+ </section>
403
+ <section>
404
+ <h2>Комментарии</h2>
405
+ <div v-for="comment in comments" :key="comment.id">{{ comment.text }}</div>
406
+ </section>
407
+ </div>
408
+ </template>
409
+ ```
410
+
411
+ ## Глобальные условия
412
+
413
+ Функция `setApiRefGlobalConditions` позволяет установить глобальное условие для всех useApiRef.
414
+
415
+ ```javascript
416
+ import { setApiRefGlobalConditions, useApiRef } from '@dxtmisha/functional'
417
+ import { ref } from 'vue'
418
+
419
+ // Глобальное условие - например, статус авторизации
420
+ const isAuthenticated = ref(false)
421
+ setApiRefGlobalConditions(isAuthenticated)
422
+
423
+ // Теперь все useApiRef будут учитывать это условие
424
+ const { data: profile } = useApiRef('/api/profile')
425
+ const { data: settings } = useApiRef('/api/settings')
426
+
427
+ // Запросы не выполнятся, пока isAuthenticated === false
428
+ isAuthenticated.value = true // Все запросы выполнятся автоматически
429
+ ```
430
+
431
+ ## Интеграция с Api классом
432
+
433
+ useApiRef использует класс Api, поэтому все настройки Api применяются автоматически:
434
+
435
+ ```javascript
436
+ import { Api, useApiRef } from '@dxtmisha/functional'
437
+
438
+ // Настройка базового URL и заголовков
439
+ Api.setUrl('/api/v1/')
440
+ Api.setHeaders({
441
+ 'Authorization': 'Bearer token123',
442
+ 'Accept': 'application/json'
443
+ })
444
+
445
+ // useApiRef автоматически использует эти настройки
446
+ const { data } = useApiRef('/users') // Запрос к /api/v1/users с заголовками
447
+ ```
448
+
449
+ ## TypeScript
450
+
451
+ ```typescript
452
+ interface User {
453
+ id: number
454
+ name: string
455
+ email: string
456
+ }
457
+
458
+ interface ApiResponse<T> {
459
+ data: T[]
460
+ total: number
461
+ }
462
+
463
+ // Типизация данных ответа
464
+ const { data } = useApiRef<User[]>('/api/users')
465
+
466
+ // Типизация с трансформацией
467
+ const { data } = useApiRef<User[], ApiResponse<User>>(
468
+ '/api/users',
469
+ undefined,
470
+ true,
471
+ undefined,
472
+ (response) => response.data // response типизирован как ApiResponse<User>
473
+ )
474
+ ```
475
+
476
+ ## Особенности поведения
477
+
478
+ ### Ленивая инициализация
479
+
480
+ Запрос не выполняется до первого обращения к `data`:
481
+
482
+ ```javascript
483
+ const api = useApiRef('/api/users')
484
+
485
+ // Запрос еще не выполнен
486
+ console.log('Композабл создан')
487
+
488
+ // Запрос выполнится при первом обращении к data
489
+ console.log(api.data.value)
490
+ ```
491
+
492
+ ### Автоматическая реактивность
493
+
494
+ При использовании ref или computed в параметрах, запросы выполняются автоматически:
495
+
496
+ ```javascript
497
+ const userId = ref(1)
498
+ const { data } = useApiRef(computed(() => `/api/users/${userId.value}`))
499
+
500
+ // Каждое изменение userId вызывает новый запрос
501
+ userId.value = 2 // Запрос к /api/users/2
502
+ userId.value = 3 // Запрос к /api/users/3
503
+ ```
504
+
505
+ ### Предотвращение дублирования
506
+
507
+ Если запрос уже выполняется, новый запрос не начнётся:
508
+
509
+ ```javascript
510
+ const { reset, loading } = useApiRef('/api/users')
511
+
512
+ await reset() // Первый запрос
513
+ // loading.value === true
514
+
515
+ await reset() // Этот вызов будет проигнорирован, пока первый не завершится
516
+ ```
517
+