docs: создать правила для синхронизации данных, layout и статистических компонентов
- DATA_SYNCHRONIZATION_RULES.md - правила синхронизации между компонентами - GRAPHQL_CACHE_RULES.md - настройки кеширования и fetchPolicy - CSS_LAYOUT_SCROLL_RULES.md - решение проблем с overflow и scroll - STATISTICAL_COMPONENTS_RULES.md - правила Master-Detail архитектуры Документация основана на исправлениях в кабинете фулфилмента 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
390
docs/api-layer/GRAPHQL_CACHE_RULES.md
Normal file
390
docs/api-layer/GRAPHQL_CACHE_RULES.md
Normal file
@ -0,0 +1,390 @@
|
||||
# 🔄 ПРАВИЛА КЕШИРОВАНИЯ GRAPHQL И FETCHPOLICY
|
||||
|
||||
> **Цель:** Обеспечить корректное кеширование GraphQL данных и предотвратить проблемы синхронизации между компонентами
|
||||
|
||||
## 📋 **ОСНОВНЫЕ ПРИНЦИПЫ КЕШИРОВАНИЯ**
|
||||
|
||||
### 1. **СИНХРОНИЗАЦИЯ СВЯЗАННЫХ КОМПОНЕНТОВ**
|
||||
|
||||
```typescript
|
||||
// ✅ ПРАВИЛЬНО - одинаковые настройки для связанных данных
|
||||
const masterComponent = useQuery(GET_WAREHOUSE_STATS, {
|
||||
fetchPolicy: 'cache-and-network',
|
||||
pollInterval: 30000,
|
||||
errorPolicy: 'all',
|
||||
})
|
||||
|
||||
const detailComponent = useQuery(GET_SUPPLIES_DETAILS, {
|
||||
fetchPolicy: 'cache-and-network', // ← ОБЯЗАТЕЛЬНО то же самое!
|
||||
pollInterval: 30000, // ← ОБЯЗАТЕЛЬНО то же самое!
|
||||
errorPolicy: 'all', // ← ОБЯЗАТЕЛЬНО то же самое!
|
||||
})
|
||||
```
|
||||
|
||||
```typescript
|
||||
// ❌ НЕПРАВИЛЬНО - разные настройки создают рассинхронизацию
|
||||
const masterComponent = useQuery(GET_WAREHOUSE_STATS, {
|
||||
fetchPolicy: 'cache-and-network',
|
||||
})
|
||||
|
||||
const detailComponent = useQuery(GET_SUPPLIES_DETAILS, {
|
||||
// default fetchPolicy: 'cache-first' - СОЗДАЁТ ПРОБЛЕМУ!
|
||||
})
|
||||
```
|
||||
|
||||
### 2. **ОБЯЗАТЕЛЬНЫЕ FETCH POLICIES ПО ТИПАМ КОМПОНЕНТОВ**
|
||||
|
||||
#### **Dashboard и Статистика**
|
||||
|
||||
```typescript
|
||||
useQuery(DASHBOARD_QUERY, {
|
||||
fetchPolicy: 'cache-and-network', // Всегда актуальные данные
|
||||
pollInterval: 30000, // Обновление каждые 30 сек
|
||||
errorPolicy: 'all', // Показывать частичные данные при ошибках
|
||||
})
|
||||
```
|
||||
|
||||
#### **Связанные компоненты (Master-Detail)**
|
||||
|
||||
```typescript
|
||||
// Все компоненты одной функциональности ДОЛЖНЫ иметь идентичные настройки
|
||||
const sharedQueryOptions = {
|
||||
fetchPolicy: 'cache-and-network' as const,
|
||||
pollInterval: 30000,
|
||||
errorPolicy: 'all' as const,
|
||||
}
|
||||
|
||||
// Применение во всех связанных компонентах
|
||||
useQuery(MASTER_QUERY, sharedQueryOptions)
|
||||
useQuery(DETAIL_QUERY, sharedQueryOptions)
|
||||
useQuery(STATS_QUERY, sharedQueryOptions)
|
||||
```
|
||||
|
||||
#### **Списки и таблицы**
|
||||
|
||||
```typescript
|
||||
useQuery(LIST_QUERY, {
|
||||
fetchPolicy: 'cache-and-network', // Актуальные данные
|
||||
pollInterval: 60000, // 1 минута для списков
|
||||
notifyOnNetworkStatusChange: true, // Показывать статус загрузки
|
||||
})
|
||||
```
|
||||
|
||||
#### **Редко изменяющиеся данные**
|
||||
|
||||
```typescript
|
||||
useQuery(STATIC_DATA_QUERY, {
|
||||
fetchPolicy: 'cache-first', // Кеш приоритетен
|
||||
pollInterval: 300000, // 5 минут
|
||||
errorPolicy: 'ignore', // Не показывать ошибки для статичных данных
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏢 **СПЕЦИФИЧЕСКИЕ ПРАВИЛА ДЛЯ ФУЛФИЛМЕНТА**
|
||||
|
||||
### **ГРУППЫ СИНХРОНИЗИРОВАННЫХ КОМПОНЕНТОВ**
|
||||
|
||||
#### **Группа 1: Складская статистика**
|
||||
|
||||
```typescript
|
||||
// Все эти компоненты ДОЛЖНЫ использовать одинаковые настройки:
|
||||
const warehouseStatsOptions = {
|
||||
fetchPolicy: 'cache-and-network' as const,
|
||||
pollInterval: 30000,
|
||||
errorPolicy: 'all' as const,
|
||||
}
|
||||
|
||||
// 1. Главный dashboard (/fulfillment-warehouse)
|
||||
useQuery(GET_WAREHOUSE_STATS, warehouseStatsOptions)
|
||||
|
||||
// 2. Подраздел расходников (/fulfillment-warehouse/supplies)
|
||||
useQuery(GET_SUPPLIES_STATS, warehouseStatsOptions)
|
||||
|
||||
// 3. Раздел услуг (/services) - вкладка "Расходники"
|
||||
useQuery(GET_SERVICE_SUPPLIES, warehouseStatsOptions)
|
||||
```
|
||||
|
||||
#### **Группа 2: История поставок**
|
||||
|
||||
```typescript
|
||||
const suppliesHistoryOptions = {
|
||||
fetchPolicy: 'cache-and-network' as const,
|
||||
pollInterval: 30000,
|
||||
errorPolicy: 'all' as const,
|
||||
}
|
||||
|
||||
// 1. Основные поставки (главная таблица)
|
||||
useQuery(GET_MY_FULFILLMENT_SUPPLIES, suppliesHistoryOptions)
|
||||
|
||||
// 2. Детали поставок (раскрывающиеся строки)
|
||||
useQuery(GET_MY_FULFILLMENT_CONSUMABLE_SUPPLIES, suppliesHistoryOptions)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 **КРИТИЧЕСКИЕ АНТИ-ПАТТЕРНЫ**
|
||||
|
||||
### **❌ НИКОГДА НЕ ДЕЛАЙТЕ:**
|
||||
|
||||
#### **1. Разные fetchPolicy для связанных данных**
|
||||
|
||||
```typescript
|
||||
// ❌ СОЗДАЁТ РАССИНХРОНИЗАЦИЮ
|
||||
const warehouse = useQuery(GET_WAREHOUSE, { fetchPolicy: 'cache-and-network' })
|
||||
const supplies = useQuery(GET_SUPPLIES, {}) // default cache-first
|
||||
|
||||
// Результат: warehouse показывает новые данные, supplies - старые
|
||||
```
|
||||
|
||||
#### **2. Разные pollInterval для одной функциональности**
|
||||
|
||||
```typescript
|
||||
// ❌ СОЗДАЁТ НЕСОГЛАСОВАННОСТЬ
|
||||
const stats = useQuery(GET_STATS, { pollInterval: 30000 })
|
||||
const details = useQuery(GET_DETAILS, { pollInterval: 60000 })
|
||||
|
||||
// Результат: stats обновляется чаще details - значения не совпадают
|
||||
```
|
||||
|
||||
#### **3. Смешивание cache-first и cache-and-network**
|
||||
|
||||
```typescript
|
||||
// ❌ КЛАССИЧЕСКАЯ ОШИБКА
|
||||
const mainData = useQuery(MAIN_QUERY, { fetchPolicy: 'cache-and-network' })
|
||||
const relatedData = useQuery(RELATED_QUERY, { fetchPolicy: 'cache-first' })
|
||||
|
||||
// Результат: mainData актуальные, relatedData устаревшие
|
||||
```
|
||||
|
||||
#### **4. Игнорирование ошибок в критических компонентах**
|
||||
|
||||
```typescript
|
||||
// ❌ СКРЫВАЕТ ПРОБЛЕМЫ
|
||||
useQuery(CRITICAL_DATA_QUERY, {
|
||||
errorPolicy: 'ignore', // Не показывает ошибки когда нужно!
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 **ПРАВИЛА ПО ТИПАМ ДАННЫХ**
|
||||
|
||||
### **СТАТИСТИКА И DASHBOARD**
|
||||
|
||||
```typescript
|
||||
const dashboardOptions = {
|
||||
fetchPolicy: 'cache-and-network', // Всегда актуальные данные
|
||||
pollInterval: 30000, // 30 сек - критическая актуальность
|
||||
errorPolicy: 'all', // Показывать частичные данные
|
||||
notifyOnNetworkStatusChange: true, // Показывать индикатор обновления
|
||||
}
|
||||
```
|
||||
|
||||
### **ИНВЕНТАРЬ И ОСТАТКИ**
|
||||
|
||||
```typescript
|
||||
const inventoryOptions = {
|
||||
fetchPolicy: 'cache-and-network', // Остатки должны быть точными
|
||||
pollInterval: 30000, // Частое обновление
|
||||
errorPolicy: 'all', // Критические данные
|
||||
}
|
||||
```
|
||||
|
||||
### **ИСТОРИЯ И ЛОГИ**
|
||||
|
||||
```typescript
|
||||
const historyOptions = {
|
||||
fetchPolicy: 'cache-first', // История не меняется часто
|
||||
pollInterval: 120000, // 2 минуты достаточно
|
||||
errorPolicy: 'all', // Показывать что есть
|
||||
}
|
||||
```
|
||||
|
||||
### **СПРАВОЧНИКИ И КАТАЛОГИ**
|
||||
|
||||
```typescript
|
||||
const catalogOptions = {
|
||||
fetchPolicy: 'cache-first', // Справочники стабильны
|
||||
pollInterval: 300000, // 5 минут
|
||||
errorPolicy: 'ignore', // Не критично
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 **ПРАКТИЧЕСКИЕ ПРИМЕРЫ ИСПРАВЛЕНИЙ**
|
||||
|
||||
### **ПРИМЕР 1: Рассинхронизация карточек статистики**
|
||||
|
||||
**❌ Проблема:**
|
||||
|
||||
```typescript
|
||||
// Главный dashboard
|
||||
const warehouseStats = useQuery(GET_WAREHOUSE_STATS, {
|
||||
fetchPolicy: 'cache-and-network',
|
||||
})
|
||||
|
||||
// Подраздел supplies
|
||||
const suppliesStats = useQuery(GET_SUPPLIES_STATS, {
|
||||
// default fetchPolicy: 'cache-first' - ПРОБЛЕМА!
|
||||
})
|
||||
|
||||
// Результат: карточка "РАСХОДНИКИ ФУЛФИЛМЕНТА" = 130, карточка "ОСТАТОК" = 160
|
||||
```
|
||||
|
||||
**✅ Решение:**
|
||||
|
||||
```typescript
|
||||
// Общие настройки для всех статистических компонентов
|
||||
const statsOptions = {
|
||||
fetchPolicy: 'cache-and-network' as const,
|
||||
pollInterval: 30000,
|
||||
errorPolicy: 'all' as const,
|
||||
}
|
||||
|
||||
// Применяем везде одинаково
|
||||
const warehouseStats = useQuery(GET_WAREHOUSE_STATS, statsOptions)
|
||||
const suppliesStats = useQuery(GET_SUPPLIES_STATS, statsOptions)
|
||||
```
|
||||
|
||||
### **ПРИМЕР 2: История поставок не синхронизируется с основной таблицей**
|
||||
|
||||
**❌ Проблема:**
|
||||
|
||||
```typescript
|
||||
// Основная таблица
|
||||
const supplies = useQuery(GET_SUPPLIES, { fetchPolicy: 'cache-and-network' })
|
||||
|
||||
// История в раскрывающихся строках
|
||||
const history = useQuery(GET_SUPPLY_HISTORY, {}) // default cache-first
|
||||
|
||||
// Результат: основная таблица обновилась, история показывает старые данные
|
||||
```
|
||||
|
||||
**✅ Решение:**
|
||||
|
||||
```typescript
|
||||
const syncedOptions = {
|
||||
fetchPolicy: 'cache-and-network',
|
||||
pollInterval: 30000,
|
||||
}
|
||||
|
||||
const supplies = useQuery(GET_SUPPLIES, syncedOptions)
|
||||
const history = useQuery(GET_SUPPLY_HISTORY, syncedOptions)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **ЧЕКЛИСТ НАСТРОЙКИ КЕШИРОВАНИЯ**
|
||||
|
||||
### **Перед релизом компонента:**
|
||||
|
||||
- [ ] Определили группу связанных компонентов
|
||||
- [ ] Выбрали единую fetchPolicy для группы
|
||||
- [ ] Установили одинаковые pollInterval значения
|
||||
- [ ] Настроили errorPolicy в соответствии с критичностью
|
||||
- [ ] Протестировали синхронизацию данных
|
||||
|
||||
### **При обнаружении рассинхронизации:**
|
||||
|
||||
- [ ] Проверили fetchPolicy всех связанных запросов
|
||||
- [ ] Сравнили pollInterval значения
|
||||
- [ ] Убедились в использовании одних таблиц БД
|
||||
- [ ] Проверили последовательность обновления данных
|
||||
|
||||
---
|
||||
|
||||
## 📈 **МОНИТОРИНГ И ОТЛАДКА**
|
||||
|
||||
### **DEBUG ЛОГИ ДЛЯ КЕШИРОВАНИЯ**
|
||||
|
||||
```typescript
|
||||
// Добавляйте в компоненты для контроля кеширования
|
||||
useQuery(QUERY, {
|
||||
fetchPolicy: 'cache-and-network',
|
||||
onCompleted: (data) => {
|
||||
console.log('CACHE DEBUG:', {
|
||||
query: 'QUERY_NAME',
|
||||
timestamp: new Date().toISOString(),
|
||||
dataLength: data?.items?.length,
|
||||
source: 'network/cache',
|
||||
})
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('CACHE ERROR:', {
|
||||
query: 'QUERY_NAME',
|
||||
error: error.message,
|
||||
networkError: error.networkError?.message,
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### **ПРОВЕРКА СИНХРОНИЗАЦИИ**
|
||||
|
||||
```typescript
|
||||
// Контрольные точки для проверки консистентности
|
||||
useEffect(() => {
|
||||
if (masterData && detailData) {
|
||||
const masterValue = masterData.total
|
||||
const detailValue = detailData.reduce((sum, item) => sum + item.value, 0)
|
||||
|
||||
if (Math.abs(masterValue - detailValue) > 0) {
|
||||
console.error('🚨 CACHE SYNC ERROR:', {
|
||||
component: 'ComponentName',
|
||||
master: masterValue,
|
||||
detail: detailValue,
|
||||
diff: masterValue - detailValue,
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [masterData, detailData])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 **РЕКОМЕНДАЦИИ ПО ПРОИЗВОДИТЕЛЬНОСТИ**
|
||||
|
||||
### **ОПТИМИЗАЦИЯ POLLING**
|
||||
|
||||
```typescript
|
||||
// Адаптивные интервалы в зависимости от активности пользователя
|
||||
const useAdaptivePolling = () => {
|
||||
const [isActive, setIsActive] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const handleVisibilityChange = () => {
|
||||
setIsActive(!document.hidden)
|
||||
}
|
||||
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange)
|
||||
return () => document.removeEventListener('visibilitychange', handleVisibilityChange)
|
||||
}, [])
|
||||
|
||||
return {
|
||||
pollInterval: isActive ? 30000 : 120000, // Реже обновляем неактивные вкладки
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **УСЛОВНОЕ КЕШИРОВАНИЕ**
|
||||
|
||||
```typescript
|
||||
// Разные стратегии для разных сценариев
|
||||
const getCachePolicy = (dataType: string) => {
|
||||
switch (dataType) {
|
||||
case 'critical-stats':
|
||||
return { fetchPolicy: 'cache-and-network', pollInterval: 30000 }
|
||||
case 'user-lists':
|
||||
return { fetchPolicy: 'cache-and-network', pollInterval: 60000 }
|
||||
case 'reference-data':
|
||||
return { fetchPolicy: 'cache-first', pollInterval: 300000 }
|
||||
default:
|
||||
return { fetchPolicy: 'cache-first' }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Следование этим правилам обеспечит стабильное кеширование и синхронизацию GraphQL данных!** 🚀
|
Reference in New Issue
Block a user