Расскажите о сложной проблеме, которую вы решали в React Native проекте.

Одна из наиболее сложных проблем, с которой пришлось столкнуться в React Native проекте, была связана с интеграцией высоконагруженного видеофида (аналогично TikTok/Instagram Reels) с автопроигрыванием, бесконечной прокруткой, предзагрузкой видео, управлением звуком, UI-анимациями и кэшированием. Это был основной экран приложения, и от его плавности и отзывчивости напрямую зависело пользовательское восприятие качества продукта.

Основные цели и сложности

  1. Отображение вертикального фида с видеороликами.

  2. Автоматическое воспроизведение текущего видео, остановка предыдущего.

  3. Плавный переход между видео с анимацией.

  4. Предзагрузка соседних видео (вверх и вниз).

  5. Кеширование видеофайлов, чтобы не нагружать сеть.

  6. Уменьшение потребления памяти и тормозов на Android.

Сложность заключалась в том, что даже в ранней версии экрана:

  • UI начинал лагать уже при прокрутке 3–4 видео;

  • Звук иногда начинался с задержкой;

  • Видео не воспроизводилось плавно;

  • Список часто ререндерился при любом изменении.

Анализ и инструменты

1. Анализ производительности

  • Использовался Flipper + Perf Monitor для анализа JS Thread.

  • why-did-you-render показывал лишние ререндеры.

  • react-native-performance помог отслеживать потерю кадров на Android.

2. Профилирование прокрутки

  • FlatList с initialNumToRender: 3, но не настроены windowSize и getItemLayout.

  • Были видны скачки FPS при первом запуске видео и при переходе между элементами.

3. Проблемы с видео

  • Использовалась библиотека react-native-video, но с ошибками:

    • Видео загружалось каждый раз при маунте.

    • При быстром скролле проигрывались сразу несколько видео.

    • Проблемы с управлением состоянием (pause/play) при переходе между карточками.

Подход к решению

1. Управление видео через ref и index

Создан глобальный провайдер, отслеживающий активный индекс. Каждый компонент VideoCard получает свой index, сравнивает с активным и запускает/останавливает видео:

useEffect(() => {
if (currentIndex === index) {
videoRef.current?.playAsync();
} else {
videoRef.current?.pauseAsync();
}
}, \[currentIndex\]);

2. FlatList оптимизация

  • windowSize: 5

  • maxToRenderPerBatch: 2

  • removeClippedSubviews: true

  • getItemLayout: так как все карточки были одинаковой высоты

Это снизило нагрузку на память и сократило число рендеров.

3. Кеширование видео

Перешли на react-native-video-cache + react-native-fs. Видео загружались и сохранялись локально, проверялись по URL. При повторном открытии они подгружались из кеша. Это убрало зависание при первом старте.

4. Отложенная загрузка и prefetch

Добавлено предзапрос видео по индексу current + 1 и current - 1 с помощью:

useEffect(() => {
prefetchVideo(videoList\[currentIndex + 1\]?.url);
prefetchVideo(videoList\[currentIndex - 1\]?.url);
}, \[currentIndex\]);

5. Ограничение на количество одновременно активных видео

Ввелся центральный контроллер через контекст, который позволял проигрываться только одному видео. Остальные получали команду на pause.

6. Анимации с Reanimated 2

Изначально анимации перелистывания были через Animated, что загружало JS Thread. Перешли на Reanimated 2 с использованием Worklets. Пример:

useAnimatedScrollHandler({
onScroll: (event) => {
scrollY.value = event.contentOffset.y;
},
});

Вся логика прокрутки и определения активного индекса была вынесена в UI Thread.

7. Обработка ошибок

Добавлена fallback-логика:

  • При ошибке загрузки — показывался заглушечный кадр.

  • Логгировались ошибки в Sentry.

  • Устанавливались таймауты на загрузку и паузы между действиями.

Результаты

  • Прокрутка стала плавной даже на Android mid-range (4 ГБ RAM).

  • Видео проигрывались корректно без запинок.

  • Количество одновременно активных плееров — 1.

  • Память под контролем: мониторинг показал снижение использования на 40–60 МБ.

  • Были покрыты юнит-тестами логика проигрывания и кэширования.

  • Поведение стало консистентным на Android и iOS.

Эта задача потребовала комплексного подхода: оптимизации UI, использования Reanimated, управления стейтом, работы с кешем и нативными возможностями. Основная сложность заключалась в том, что проблему нельзя было решить одной библиотекой или хуком — требовалась связка инженерных решений на нескольких уровнях.