Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m43s
386 lines
11 KiB
Markdown
386 lines
11 KiB
Markdown
# ⚡ Быстрый старт - Обработка ошибок
|
||
|
||
## 5-минутное руководство по использованию новой системы обработки ошибок
|
||
|
||
---
|
||
|
||
## 🎯 Основные сценарии
|
||
|
||
### 1️⃣ Простая загрузка данных
|
||
|
||
```jsx
|
||
import { useErrorHandler } from '../hooks/useErrorHandler';
|
||
import api from '../lib/api';
|
||
|
||
function MyComponent() {
|
||
const { withErrorHandler } = useErrorHandler();
|
||
|
||
const loadData = withErrorHandler(
|
||
async () => {
|
||
const response = await api.get('/domains-new');
|
||
setData(response.data);
|
||
},
|
||
{ successMessage: 'Данные загружены' }
|
||
);
|
||
|
||
return <button onClick={loadData}>Загрузить</button>;
|
||
}
|
||
```
|
||
|
||
**Что происходит:**
|
||
- ✅ Автоматический retry при сбоях (до 3 раз)
|
||
- ✅ User-friendly сообщения об ошибках
|
||
- ✅ Успешное уведомление при загрузке
|
||
- ✅ Нет дубликатов уведомлений
|
||
|
||
---
|
||
|
||
### 2️⃣ Кнопка с retry
|
||
|
||
```jsx
|
||
import RetryButton from '../components/RetryButton';
|
||
import api from '../lib/api';
|
||
|
||
function MyComponent() {
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
const saveData = async () => {
|
||
setLoading(true);
|
||
try {
|
||
await api.post('/domains-new', { domains: [...] });
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<RetryButton
|
||
onRetry={saveData}
|
||
loading={loading}
|
||
>
|
||
Сохранить
|
||
</RetryButton>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Что происходит:**
|
||
- ✅ Показывает индикатор загрузки
|
||
- ✅ Автоматический retry при ошибках
|
||
- ✅ Блокирует повторные клики
|
||
|
||
---
|
||
|
||
### 3️⃣ Ручная обработка ошибок
|
||
|
||
```jsx
|
||
import { useErrorHandler } from '../hooks/useErrorHandler';
|
||
import api from '../lib/api';
|
||
|
||
function MyComponent() {
|
||
const { handleError, handleSuccess } = useErrorHandler();
|
||
|
||
const deleteItem = async (id) => {
|
||
try {
|
||
await api.delete(`/domains-new/${id}`);
|
||
handleSuccess('Домен удалён');
|
||
} catch (error) {
|
||
handleError(error, {
|
||
component: 'MyComponent',
|
||
action: 'delete',
|
||
itemId: id
|
||
});
|
||
}
|
||
};
|
||
|
||
return <button onClick={() => deleteItem(123)}>Удалить</button>;
|
||
}
|
||
```
|
||
|
||
**Что происходит:**
|
||
- ✅ Полный контроль над обработкой
|
||
- ✅ Контекст для отладки
|
||
- ✅ User-friendly сообщения
|
||
|
||
---
|
||
|
||
## 🔧 Настройка (уже сделано!)
|
||
|
||
Все компоненты уже интегрированы в `App.jsx`:
|
||
|
||
```jsx
|
||
<ErrorBoundary> {/* Ловит ошибки React */}
|
||
<Router>
|
||
<QueryClientProvider>
|
||
<NotifyProvider>
|
||
<NetworkErrorHandler /> {/* Мониторит сеть */}
|
||
<MainLayout />
|
||
</NotifyProvider>
|
||
</QueryClientProvider>
|
||
</Router>
|
||
</ErrorBoundary>
|
||
```
|
||
|
||
Ничего дополнительно настраивать не нужно! 🎉
|
||
|
||
---
|
||
|
||
## 📦 Что уже работает автоматически
|
||
|
||
### ✅ Автоматический Retry
|
||
|
||
| Метод | Ошибка | Попытки |
|
||
|-------|--------|---------|
|
||
| GET | Network | 3 |
|
||
| GET | Timeout | 2 |
|
||
| GET | Server (5xx) | 2 |
|
||
| POST/PUT | Network | 1 |
|
||
| POST/PUT | Timeout | 1 |
|
||
|
||
### ✅ User-Friendly сообщения
|
||
|
||
**Технические ошибки** → **Понятные сообщения**
|
||
|
||
- `ECONNABORTED` → "Сервер не отвечает"
|
||
- `500 Internal Server Error` → "Ошибка сервера. Попробуйте позже"
|
||
- `400 Bad Request` → "Ошибка валидации. Проверьте данные"
|
||
- `404 Not Found` → "Запрашиваемый ресурс не найден"
|
||
|
||
### ✅ Offline Detection
|
||
|
||
- Красный баннер при потере связи
|
||
- Зелёный баннер при восстановлении
|
||
- Автоматическое скрытие через 3 секунды
|
||
|
||
### ✅ Error Boundary
|
||
|
||
- Ловит ошибки рендеринга React
|
||
- Показывает красивую страницу ошибки
|
||
- Кнопки восстановления
|
||
|
||
---
|
||
|
||
## 🎨 Типы уведомлений
|
||
|
||
```jsx
|
||
const { handleSuccess, handleError, handleWarning, handleInfo } = useErrorHandler();
|
||
|
||
handleSuccess('Данные сохранены'); // 🟢 Зелёное
|
||
handleError('Не удалось загрузить'); // 🔴 Красное
|
||
handleWarning('Изменения не сохранены'); // 🟡 Жёлтое
|
||
handleInfo('Проверьте обновления'); // 🔵 Синее
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 Продвинутые примеры
|
||
|
||
### React Query интеграция
|
||
|
||
```jsx
|
||
import { useQuery } from '@tanstack/react-query';
|
||
import { useErrorHandler } from '../hooks/useErrorHandler';
|
||
import api from '../lib/api';
|
||
|
||
function MyComponent() {
|
||
const { handleError } = useErrorHandler();
|
||
|
||
const { data, isLoading, refetch } = useQuery({
|
||
queryKey: ['domains'],
|
||
queryFn: async () => {
|
||
const res = await api.get('/domains-new');
|
||
return res.data;
|
||
},
|
||
onError: (error) => {
|
||
handleError(error, { query: 'domains' });
|
||
},
|
||
retry: 3 // React Query также делает retry
|
||
});
|
||
|
||
return <div>{/* ... */}</div>;
|
||
}
|
||
```
|
||
|
||
### Условная обработка
|
||
|
||
```jsx
|
||
import { useErrorHandler } from '../hooks/useErrorHandler';
|
||
import { isCriticalError } from '../lib/api';
|
||
import api from '../lib/api';
|
||
|
||
function MyComponent() {
|
||
const { handleError } = useErrorHandler();
|
||
|
||
const loadData = async () => {
|
||
try {
|
||
const res = await api.get('/critical-data');
|
||
setData(res.data);
|
||
} catch (error) {
|
||
if (isCriticalError(error)) {
|
||
// Критичная ошибка - перенаправить
|
||
handleError(error);
|
||
navigate('/error');
|
||
} else {
|
||
// Некритичная - показать уведомление
|
||
handleError(error);
|
||
}
|
||
}
|
||
};
|
||
|
||
return <button onClick={loadData}>Загрузить</button>;
|
||
}
|
||
```
|
||
|
||
### Batch операции
|
||
|
||
```jsx
|
||
import { useErrorHandler } from '../hooks/useErrorHandler';
|
||
import api from '../lib/api';
|
||
|
||
function MyComponent() {
|
||
const { handleError, handleSuccess } = useErrorHandler();
|
||
|
||
const deleteMultiple = async (ids) => {
|
||
const errors = [];
|
||
|
||
for (const id of ids) {
|
||
try {
|
||
await api.delete(`/domains-new/${id}`);
|
||
} catch (error) {
|
||
errors.push({ id, error });
|
||
}
|
||
}
|
||
|
||
if (errors.length === 0) {
|
||
handleSuccess(`Удалено ${ids.length} элементов`);
|
||
} else {
|
||
handleError(new Error(`Не удалось удалить ${errors.length} элементов`), {
|
||
errors
|
||
});
|
||
}
|
||
};
|
||
|
||
return <button onClick={() => deleteMultiple([1, 2, 3])}>
|
||
Удалить выбранные
|
||
</button>;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 📚 Дополнительная документация
|
||
|
||
- **Полное руководство:** [ERROR_HANDLING_GUIDE.md](./ERROR_HANDLING_GUIDE.md)
|
||
- **Схемы работы:** [ERROR_HANDLING_FLOW.md](./ERROR_HANDLING_FLOW.md)
|
||
- **Примеры кода:** [frontend/src/examples/ErrorHandlingExample.jsx](./frontend/src/examples/ErrorHandlingExample.jsx)
|
||
- **Краткое резюме:** [ERROR_HANDLING_SUMMARY.md](./ERROR_HANDLING_SUMMARY.md)
|
||
|
||
---
|
||
|
||
## 🐛 Отладка
|
||
|
||
### Посмотреть детали ошибки
|
||
|
||
В уведомлении кликните **"Подробнее"**:
|
||
|
||
```
|
||
Детали:
|
||
{
|
||
"type": "NETWORK",
|
||
"status": null,
|
||
"message": "Не удалось подключиться к серверу",
|
||
"requestId": "abc123",
|
||
"url": "/api/domains-new",
|
||
"method": "GET",
|
||
"timestamp": "2025-10-03T15:30:45.123Z",
|
||
"action": "Проверьте подключение к интернету"
|
||
}
|
||
```
|
||
|
||
### Консоль браузера
|
||
|
||
Все ошибки логируются в консоль:
|
||
|
||
```javascript
|
||
// Открыть DevTools (F12) → Console
|
||
console.error('API Error:', {
|
||
type: 'NETWORK',
|
||
retryAttempt: 3,
|
||
maxRetries: 3,
|
||
// ...
|
||
});
|
||
```
|
||
|
||
### Очистить кэш
|
||
|
||
```javascript
|
||
// В консоли браузера
|
||
localStorage.clear();
|
||
sessionStorage.clear();
|
||
window.location.reload();
|
||
```
|
||
|
||
---
|
||
|
||
## ❓ FAQ
|
||
|
||
**Q: Нужно ли обрабатывать каждую ошибку вручную?**
|
||
A: Нет! API автоматически показывает уведомления. Используйте `handleError` только для локальной логики.
|
||
|
||
**Q: Как отключить автоматические уведомления?**
|
||
A: Используйте `withErrorHandler` с опцией `silent: true`:
|
||
```jsx
|
||
withErrorHandler(fn, { silent: true })
|
||
```
|
||
|
||
**Q: Как изменить количество retry попыток?**
|
||
A: Отредактируйте `frontend/src/lib/apiErrorHandler.js` → `getMaxRetries()`
|
||
|
||
**Q: Работает ли offline?**
|
||
A: Частично. Приложение детектирует offline, но не сохраняет данные локально. Это можно добавить позже.
|
||
|
||
**Q: Как интегрировать с Sentry?**
|
||
A: См. [ERROR_HANDLING_GUIDE.md](./ERROR_HANDLING_GUIDE.md) → Раздел "Настройка"
|
||
|
||
---
|
||
|
||
## ✅ Чек-лист интеграции
|
||
|
||
- [x] ErrorBoundary добавлен в App.jsx
|
||
- [x] NetworkErrorHandler добавлен в App.jsx
|
||
- [x] api.js обновлён с улучшенным retry
|
||
- [x] useErrorHandler хук создан
|
||
- [x] RetryButton компонент создан
|
||
- [x] apiErrorHandler утилиты созданы
|
||
- [x] Документация написана
|
||
- [x] Примеры кода добавлены
|
||
- [x] Линтер проверки пройдены
|
||
|
||
**Статус: ✅ Готово к использованию!**
|
||
|
||
---
|
||
|
||
## 🎉 Начните использовать прямо сейчас!
|
||
|
||
1. Запустите приложение:
|
||
```powershell
|
||
cd backend && npm start
|
||
cd frontend && npm run dev
|
||
```
|
||
|
||
2. Импортируйте хук в любой компонент:
|
||
```jsx
|
||
import { useErrorHandler } from '../hooks/useErrorHandler';
|
||
```
|
||
|
||
3. Используйте `withErrorHandler` или `handleError`
|
||
|
||
4. Наслаждайтесь автоматической обработкой ошибок! 🚀
|
||
|
||
---
|
||
|
||
**Вопросы?** → [ERROR_HANDLING_GUIDE.md](./ERROR_HANDLING_GUIDE.md)
|
||
**Проблемы?** → Проверьте консоль браузера
|
||
**Идеи?** → Создайте issue или PR
|
||
|