import {Meta} from '@storybook/addon-docs/blocks'

<Meta title='@dxtmisha/functional/ru/Composables/useApiRef'/>

# Композабл useApiRef

Композабл для работы с HTTP-запросами в Vue 3 приложениях. Предоставляет реактивный интерфейс для выполнения API запросов с автоматическим управлением состоянием загрузки, поддержкой реактивности параметров, условным выполнением и трансформацией данных.

## Основные возможности

- **Реактивные запросы** — автоматическое выполнение запросов при изменении параметров
- **Управление состоянием** — автоматическое отслеживание состояния загрузки и готовности данных
- **Условное выполнение** — возможность выполнять запросы только при выполнении условий
- **Трансформация данных** — преобразование полученных данных перед сохранением
- **Интеграция с Api** — использует класс Api для выполнения запросов с поддержкой кеширования
- **Глобальные условия** — возможность установить глобальные условия для всех useApiRef
- **Автоочистка** — опциональная очистка данных при размонтировании компонента
- **TypeScript поддержка** — полная типизация с дженериками

## Базовое использование

### Простой GET запрос

```javascript
import { useApiRef } from '@dxtmisha/functional'

// Выполняет GET запрос при монтировании компонента
const { data, loading, isStarting, reading } = useApiRef('/api/users')

// data - ref с данными ответа
// loading - ref с состоянием загрузки
// isStarting - computed, true до первой загрузки данных
// reading - computed, true во время чтения данных
```

### Использование в компоненте

```vue
<script setup>
import { useApiRef } from '@dxtmisha/functional'

const { data: users, loading } = useApiRef('/api/users')
</script>

<template>
  <div v-if="loading">Загрузка...</div>
  <div v-else-if="users">
    <div v-for="user in users" :key="user.id">
      {{ user.name }}
    </div>
  </div>
</template>
```

## Параметры

### `path`

Путь к API endpoint. Может быть строкой или ref-ом для реактивности.

**Тип:** `RefOrNormal<string | undefined>`

```javascript
// Статический путь
const { data } = useApiRef('/api/users')

// Реактивный путь
const userId = ref(1)
const { data: user } = useApiRef(computed(() => `/api/users/${userId.value}`))

// При изменении userId автоматически выполняется новый запрос
userId.value = 2
```

### `options`

Параметры запроса. Может быть HTTP методом, объектом настроек или ref-ом.

**Тип:** `ApiMethodItem | RefOrNormal<ApiFetch>`

```javascript
// Указать только метод
const { data } = useApiRef('/api/users', 'POST')

// Полные настройки
const { data } = useApiRef('/api/users', {
  method: 'POST',
  request: { name: 'Иван', email: 'ivan@example.com' },
  headers: { 'Content-Type': 'application/json' }
})

// Реактивные параметры
const params = ref({ page: 1, limit: 10 })
const { data } = useApiRef('/api/users', computed(() => ({
  method: 'GET',
  request: params.value
})))

// При изменении params автоматически выполняется новый запрос
params.value = { page: 2, limit: 10 }
```

### `reactivity`

Включить или отключить автоматическое выполнение при изменении параметров.

**Тип:** `boolean`
**По умолчанию:** `true`

```javascript
// С реактивностью (по умолчанию)
const params = ref({ page: 1 })
const { data, reset } = useApiRef('/api/users', { request: params }, true)

params.value.page = 2 // Автоматически выполнится запрос

// Без реактивности
const { data, reset } = useApiRef('/api/users', { request: params }, false)

params.value.page = 2 // Запрос НЕ выполнится
await reset() // Нужно вызвать reset вручную
```

### `conditions`

Условие для выполнения запроса. Запрос выполняется только если условие истинно.

**Тип:** `RefType<boolean>`

```javascript
// Запрос выполняется только когда isAuth === true
const isAuth = ref(false)
const { data } = useApiRef('/api/profile', undefined, true, isAuth)

// data будет undefined, пока isAuth === false
console.log(data.value) // undefined

isAuth.value = true // Запрос автоматически выполнится
```

### `transformation`

Функция для преобразования полученных данных перед сохранением в ref.

**Тип:** `(data: T) => R`

```javascript
// Преобразование данных API в нужный формат
const { data } = useApiRef(
  '/api/users',
  undefined,
  true,
  undefined,
  (response) => {
    // response - данные от API
    // Возвращаем преобразованные данные
    return response.data.map(user => ({
      id: user.id,
      fullName: `${user.firstName} ${user.lastName}`,
      isActive: user.status === 'active'
    }))
  }
)

// data содержит преобразованные данные
```

### `unmounted`

Очищать ли данные при размонтировании компонента.

**Тип:** `boolean`
**По умолчанию:** `undefined`

```javascript
// Данные будут очищены при размонтировании компонента
const { data } = useApiRef('/api/users', undefined, true, undefined, undefined, true)

// При переходе на другую страницу data станет undefined
```

## Возвращаемые значения

### `data`

Ref с данными ответа. `undefined` до первой загрузки.

**Тип:** `Ref<R | undefined>`

```javascript
const { data } = useApiRef('/api/users')

console.log(data.value) // undefined до загрузки
// После загрузки: массив пользователей
```

### `loading`

Ref с состоянием загрузки. `true` во время выполнения запроса.

**Тип:** `Ref<boolean>`

```javascript
const { loading } = useApiRef('/api/users')

console.log(loading.value) // true во время загрузки, false после
```

### `isStarting`

Computed, показывает, загружены ли данные хотя бы раз. `true` до первой загрузки.

**Тип:** `ComputedRef<boolean>`

```javascript
const { data, isStarting } = useApiRef('/api/users')

console.log(isStarting.value) // true до первой загрузки
// После первой загрузки всегда false, даже при повторных запросах
```

### `reading`

Computed, показывает активное чтение данных.

**Тип:** `ComputedRef<boolean>`

```javascript
const { reading } = useApiRef('/api/users')

console.log(reading.value) // true во время чтения данных
```

### `reset`

Функция для принудительного выполнения запроса.

**Тип:** `() => Promise<void>`

```javascript
const { data, reset } = useApiRef('/api/users')

// Принудительно перезагрузить данные
await reset()
```

## Примеры использования

### Список с фильтрами

```vue
<script setup>
import { ref, computed } from 'vue'
import { useApiRef } from '@dxtmisha/functional'

const searchQuery = ref('')
const currentPage = ref(1)

const { data: users, loading } = useApiRef(
  '/api/users',
  computed(() => ({
    method: 'GET',
    request: {
      search: searchQuery.value,
      page: currentPage.value,
      limit: 20
    }
  }))
)

const handleSearch = (query) => {
  searchQuery.value = query
  currentPage.value = 1 // Сброс на первую страницу
  // Запрос выполнится автоматически
}
</script>

<template>
  <div>
    <input v-model="searchQuery" placeholder="Поиск...">
    <div v-if="loading">Загрузка...</div>
    <div v-else>
      <div v-for="user in users" :key="user.id">{{ user.name }}</div>
      <button @click="currentPage++">Следующая страница</button>
    </div>
  </div>
</template>
```

### Условная загрузка

```vue
<script setup>
import { ref } from 'vue'
import { useApiRef } from '@dxtmisha/functional'

const showDetails = ref(false)
const userId = ref(1)

// Данные загружаются только когда showDetails === true
const { data: userDetails, loading } = useApiRef(
  computed(() => `/api/users/${userId.value}/details`),
  undefined,
  true,
  showDetails
)

const toggleDetails = () => {
  showDetails.value = !showDetails.value
  // Если showDetails стало true, запрос выполнится автоматически
}
</script>

<template>
  <button @click="toggleDetails">
    {{ showDetails ? 'Скрыть' : 'Показать' }} детали
  </button>
  <div v-if="showDetails">
    <div v-if="loading">Загрузка деталей...</div>
    <div v-else-if="userDetails">{{ userDetails }}</div>
  </div>
</template>
```

### POST запрос с трансформацией

```vue
<script setup>
import { ref } from 'vue'
import { useApiRef } from '@dxtmisha/functional'

const formData = ref({
  firstName: '',
  lastName: '',
  email: ''
})

const { data: createdUser, loading, reset } = useApiRef(
  '/api/users',
  {
    method: 'POST',
    request: formData
  },
  false, // Отключаем автоматическое выполнение
  undefined,
  (response) => {
    // Преобразуем ответ API
    return {
      id: response.id,
      fullName: `${response.firstName} ${response.lastName}`,
      email: response.email,
      createdAt: new Date(response.created_at)
    }
  }
)

const handleSubmit = async () => {
  await reset() // Выполняем запрос вручную
  if (createdUser.value) {
    console.log('Пользователь создан:', createdUser.value)
  }
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="formData.firstName" placeholder="Имя">
    <input v-model="formData.lastName" placeholder="Фамилия">
    <input v-model="formData.email" placeholder="Email">
    <button :disabled="loading">
      {{ loading ? 'Отправка...' : 'Создать' }}
    </button>
  </form>
</template>
```

### Множественные запросы

```vue
<script setup>
import { useApiRef } from '@dxtmisha/functional'

const { data: users, loading: usersLoading } = useApiRef('/api/users')
const { data: posts, loading: postsLoading } = useApiRef('/api/posts')
const { data: comments, loading: commentsLoading } = useApiRef('/api/comments')

const isLoading = computed(() =>
  usersLoading.value || postsLoading.value || commentsLoading.value
)
</script>

<template>
  <div v-if="isLoading">Загрузка данных...</div>
  <div v-else>
    <section>
      <h2>Пользователи</h2>
      <div v-for="user in users" :key="user.id">{{ user.name }}</div>
    </section>
    <section>
      <h2>Посты</h2>
      <div v-for="post in posts" :key="post.id">{{ post.title }}</div>
    </section>
    <section>
      <h2>Комментарии</h2>
      <div v-for="comment in comments" :key="comment.id">{{ comment.text }}</div>
    </section>
  </div>
</template>
```

## Глобальные условия

Функция `setApiRefGlobalConditions` позволяет установить глобальное условие для всех useApiRef.

```javascript
import { setApiRefGlobalConditions, useApiRef } from '@dxtmisha/functional'
import { ref } from 'vue'

// Глобальное условие - например, статус авторизации
const isAuthenticated = ref(false)
setApiRefGlobalConditions(isAuthenticated)

// Теперь все useApiRef будут учитывать это условие
const { data: profile } = useApiRef('/api/profile')
const { data: settings } = useApiRef('/api/settings')

// Запросы не выполнятся, пока isAuthenticated === false
isAuthenticated.value = true // Все запросы выполнятся автоматически
```

## Интеграция с Api классом

useApiRef использует класс Api, поэтому все настройки Api применяются автоматически:

```javascript
import { Api, useApiRef } from '@dxtmisha/functional'

// Настройка базового URL и заголовков
Api.setUrl('/api/v1/')
Api.setHeaders({
  'Authorization': 'Bearer token123',
  'Accept': 'application/json'
})

// useApiRef автоматически использует эти настройки
const { data } = useApiRef('/users') // Запрос к /api/v1/users с заголовками
```

## TypeScript

```typescript
interface User {
  id: number
  name: string
  email: string
}

interface ApiResponse<T> {
  data: T[]
  total: number
}

// Типизация данных ответа
const { data } = useApiRef<User[]>('/api/users')

// Типизация с трансформацией
const { data } = useApiRef<User[], ApiResponse<User>>(
  '/api/users',
  undefined,
  true,
  undefined,
  (response) => response.data // response типизирован как ApiResponse<User>
)
```

## Особенности поведения

### Ленивая инициализация

Запрос не выполняется до первого обращения к `data`:

```javascript
const api = useApiRef('/api/users')

// Запрос еще не выполнен
console.log('Композабл создан')

// Запрос выполнится при первом обращении к data
console.log(api.data.value)
```

### Автоматическая реактивность

При использовании ref или computed в параметрах, запросы выполняются автоматически:

```javascript
const userId = ref(1)
const { data } = useApiRef(computed(() => `/api/users/${userId.value}`))

// Каждое изменение userId вызывает новый запрос
userId.value = 2 // Запрос к /api/users/2
userId.value = 3 // Запрос к /api/users/3
```

### Предотвращение дублирования

Если запрос уже выполняется, новый запрос не начнётся:

```javascript
const { reset, loading } = useApiRef('/api/users')

await reset() // Первый запрос
// loading.value === true

await reset() // Этот вызов будет проигнорирован, пока первый не завершится
```

