React + TypeScript + vedro + Vite.
Температура, ветер и инсоляция: включение и выключение, регулировка прозрачности, статус загрузки и повторная попытка после ошибки. Интерфейс — простая таблица без карты.
Нужен Node.js 22.12+.
npm ci
npm run devСборка: npm run build. Просмотр сборки: npm run preview.
Проект организован по FSD:
app— подключение Vedro Provider и стили.pages— экран приложения.widgets/layers-panel— таблица и строки.features/manage-layer— управление загрузкой и прозрачностью.entities/map-layer— типы, конфигурация, store и mock API.
LayersPanel создаёт строки из конфигурации. LayerRow отвечает только за разметку и передаёт детям id и имя слоя, не подписываясь на store. Каждый компонент находится в отдельном файле.
LayerLoadingControl объединяет checkbox и повторную загрузку. LayerOpacity отвечает за ползунок и процент прозрачности, LayerStatus — за статус запроса. Асинхронная логика находится в hook useLayerLoading, обновления состояния — в helpers сущности.
Все слои хранятся в одном store, созданном через createVedro<LayersStore>. В корне приложения подключён один Vedro Provider, дополнительных Context и провайдеров отдельных слоёв нет.
type Layer = LayerDefinition & LayerState
type LayersStore = Record<LayerId, Layer>Ключи store — id слоёв (temperature, wind, insolation). Значение содержит описание слоя, enabled, opacity и resource. Начальное состояние создаётся из конфигурации; повторяющиеся id вызывают ошибку.
Helpers используют store.get и store.dispatch. При обновлении заменяется только нужный слой, остальные сохраняют ссылки. Прозрачность ограничена диапазоном 0–1; запись прежнего значения не вызывает dispatch. Статусы и ошибки хранятся в vedro, без дублирования в локальном React state.
useLayerLoading управляет запросом одного слоя. Checkbox и retry используют один экземпляр hook и общий AbortController, сохранённый в useRef.
- При включении слой сразу получает
enabled: trueи статусloading. - При выключении ссылка на текущий запрос обнуляется до вызова
abort, а статус сбрасывается вidle. Прозрачность сохраняется. - Новый запрос отменяет предыдущий. После
awaitрезультат записывается только при совпадении controller с текущим и если слой ещё включён. - Такая же проверка применяется к ошибкам. Старый
finallyне может очистить controller нового запроса. - Retry разрешён только для включённого слоя в статусе
error. При размонтировании компонент отменяет запрос и выключает слой.
При быстром переключении «включить → выключить → включить» ответ первого запроса не меняет состояние второго. Отмена останавливает ненужную работу, а проверка идентичности защищает даже от транспорта, который проигнорировал AbortSignal.
После загрузки обновляется только resource из актуального состояния store: изменение прозрачности во время запроса не откатывается.
Mock имитирует задержку API. Первый завершённый запрос ветра возвращает ошибку, повторная попытка — успех.
В исходниках нет any, все типы описаны через type. В TypeScript включены strict, noUncheckedIndexedAccess и exactOptionalPropertyTypes.
LayerResource — объединение вариантов idle, loading, success и error. Поле message доступно только после проверки status === 'error'. Поля состояния объявлены readonly и обновляются иммутабельно.
API имеет сигнатуру LoadLayer: принимает описание слоя и AbortSignal, возвращает Promise<void>. Пойманные ошибки имеют тип unknown; сообщение извлекается после проверки instanceof Error.
Дочерние компоненты используют встроенный useSelector vedro, экспортированный как useLayersSelector:
LayerLoadingControlвыбирает enabled и сообщение ошибки одним селектором.LayerOpacityвыбирает только opacity.LayerStatusвыбирает только status.
Изменение opacity вызывает обновление ползунка, но не строки, переключателя, статусов или соседних слоёв. LayerRow и список не подписаны на store.
Компоненты обёрнуты в memo, чтобы пропускать рендеры родителя с прежними props. Обработчики checkbox и retry стабильны благодаря useCallback, поэтому не нарушают мемоизацию дочерних компонентов.
В установленной версии vedro селекторы пересчитываются при каждом обновлении общего store, а результаты сравниваются через JSON.stringify. Пересчёт селектора не означает перерендер: компонент обновляется, когда выбранное значение изменилось. Id компонентов постоянны благодаря key={id}, что важно для hook библиотеки, сохраняющего первоначальный селектор.
Переход с 5 к 100+ слоям не требует изменения компонентов: слои добавляются в конфигурацию. Обновление состояния одного слоя не вызывает полную перерисовку списка — обновляются компоненты с изменившимися выбранными значениями. Это можно проверить, временно добавив console.log('rerender') в тела компонентов и изменяя прозрачность или включённость слоя. В development StrictMode возможны дополнительные вызовы рендера.
При необходимости список можно оптимизировать виртуализацией или пагинацией. При этом нужно учитывать текущее поведение: размонтирование компонента выключает слой и отменяет его запрос. Для сохранения активных слоёв при скрытии строк управление запросами потребуется отделить от их жизненного цикла.