Сначала коротко о том, зачем нам это было нужно.
Мы в основном пилим решения для фудтеха, а для мобилок используем React Native (почему, рассказывали тут).
В одном из таких проектов (российская сеть ресторанов по франшизе) нам нужно было прикрутить Яндекс Карты. Изначально хотели взять либу react-native-yamap (респект тем, кто ее делал) — но как выяснилось, она работает только на старой архитектуре RN.
После обновления до 0.76 версии, где Fabric стала использоваться по умолчанию, приложения на iOS начали падать: карта не рендерится, события не доходят до JS, приложение крашится при взаимодействии с картой и вот это вот всё. И судя по открытым тикетам, мы не одни, кто столкнулся с этой проблемой.
Полезли искать, написал ли кто-то уже библиотеку под новую архитектуру — но либо таких людей нет, либо ни с кем не делятся. Спойлер: мы пока тоже не будем, ещё обкатываем либу на своих проектах — но уже сейчас хотим рассказать, как собрали новый пакет с помощью Claude Code за два дня.

Форкать или не форкать: вот в чем вопрос
Изначально мы хотели взять эт�� либу, форкнуть, указать родительский репозиторий и с помощью ИИ-шки самим всё обновить. Чтобы понять, почему библиотека сломалась, начали разбираться, что изменилось в архитектуре React Native.
Старая архитектура работала через Bridge. JS-код сериализовал данные в JSON, отправлял через асинхронную шину в нативный слой, там происходила десериализация, выполнялся нативный код и ответ прилетал обратно тем же путём.
Новая работает через JSI. Он вызывает нативный код напрямую через C++ и получает результат без сериализации — как вы понимаете, «это другое».
Или два фундаментально разных подхода к тому, как JS разговаривает с нативом:
Paper: JS → JSON → Bridge → ObjC/Java → YMKMapView Fabric: JS → JSI → C++ → ObjC/Kotlin → YMKMapView
Какие из-за этого вылезли проблемы? Вот основные:
ViewManager’ы написаны под Paper. Для обновления нужны ViewComponentView для iOS и ViewManagerDelegate для Kotlin — а это уже больше переписывание, чем рефакторинг.
Нет Codegen-спецификаций, которые Fabric требует в обязательном порядке.
Callback’и через Bridge. Все асинхронные вызовы построены на Bridge-паттернах, а новая архитектура требует TurboModules с другой моделью взаимодействия.
События через sendEvent. В Fabric они работают через DirectEventHandler’ы с конкретным именованием — top + PascalCase. Это не критично само по себе, но вкупе означает, что придётся трогать буквально каждый файл.
Мы поняли, что если пытаться всё это добавить поверх старого кода, получится «франкенштейн»: обёртки над обёртками, костыли вместо архитектуры, ещё и риски, что что-то всё равно отвалится при следующем обновлении SDK. Плюс, влить столько изменений без поломки обратной совместимости практически нереально, а старые пользователи все еще сидят на Paper.
Поэтому мы решили переписать библиотеку с нуля: с последним SDK (за пару недель до Яндекс выпустил 4.29), поддержкой его компонентов (даже можно строить свои маршруты) и новой архитектуры (Fabric, TurboModule, полная типизация через Codegen).
Что из этого получилось
Архитектура новой библиотеки выглядит так:
src/ ├── specs/ # Codegen спецификации (источник истины) │ ├── NativeYaMap.ts │ ├── YaMapViewNativeComponent.ts │ └── ... ├── components/ # React-обёртки │ ├── YaMap.tsx │ ├── Marker.tsx │ └── ... └── modules/ # JS-модули (Suggest, Search, Geocoder) ios/ ├── Modules/ # TurboModules └── Components/ # Fabric ViewComponentView android/ ├── modules/ # TurboModules └── views/ # ViewManager + ViewManagerDelegate
Одна из проблем Fabric: Commands не поддерживают Promise-callback'и напрямую. Нельзя вызвать нативный метод и передать туда функцию, которая вызовется с результатом. Это фундаментальное ограничение модели.
Решение — паттерн с уникальными идентификаторами:
// JS: генерируем уникальный ID const callbackId = generateId(); pendingCallbacks.set(callbackId, resolve); Commands.getCameraPosition(ref, callbackId); // Native: отправляем событие с ID onCameraPositionReceived({ id, zoom, tilt, ... }) // JS: резолвим Promise по ID const callback = pendingCallbacks.get(event.id); callback(event.data);
Ещё один важный момент — композиция вместо наследования при работе с YMKMapView. Вместо того чтобы наследоваться от нативного вью напрямую, мы оборачиваем его внутрь UIView. Это даёт больше контроля над жизненным циклом и упрощает интеграцию с Fabric:
class YaMapViewComponentView: UIView { private let mapView = YMKMapView() override init(frame: CGRect) { super.init(frame: frame) addSubview(mapView) } }
Благодаря Codegen типобезопасность уже становится ответственностью компилятора — не вашей. Он генерирует интерфейсы из TypeScript-спецификаций, и нативный код обязан им соответствовать. Не реализовал метод из спеки — не скомпилируется:
class YaMapMarkerViewManager : YaMapMarkerManagerInterface<YaMapMarkerView> { // Компилятор требует реализовать ВСЕ методы из спеки override fun setLat(view: YaMapMarkerView, value: Double) override fun setLon(view: YaMapMarkerView, value: Double) // ... }
Два дня с Claude vs. две недели без него
Нативная разработка — не наш основной стек. Мы пишем на TypeScript, хорошо знаем React Native изнутри, представляем, как устроены нативные модули в теории — но Swift и Kotlin понимаем на уровне «разобраться, если очень надо».
Если бы мы стали делать либу без ИИ (и были оптимистами), это заняло бы у нас минимум две недели. Потому что пришлось бы самим копаться в документации Яндекс SDK, вручную писать обвязку для каждого нативного модуля, а каждую свифтовую ошибку, которая валится в рантайме, по часу искать и отлаживать.
Поэтому мы решили перекинуть эту работу на нашего «лучшего джуна». Или как сформулировал он сам:

Пойдём по порядку.
Начали с анализа оригинальной библиотеки. Загрузили код, попросили Claude разобраться в структуре, выделить все публичные API, понять, что именно делает каждый компонент и как он взаимодействует с Яндекс SDK. Это дало понимание, что нужно реализовать.
Прежде чем писать нативный код, спроектировали Codegen-спецификации — файлы TypeScript, которые описывают интерфейс компонентов и модулей. Claude помогал продумать типизацию, находил случаи, где типы на iOS и Android расходятся, и предлагал решения.
Собственно, генерация нативного кода для каждого компонента: сначала спека, потом iOS-реализация, затем Android. Claude писал код, объяснял решения и указывал на подводные камни Fabric.
Как этот код выглядит? Обратите внимание не только на синтаксис, но и на то, где именно происходит проверка типов.
Старый ViewManager на Java:
public class YamapViewManager extends ViewGroupManager<YamapView> { @ReactProp(name = "center") public void setCenter(YamapView view, ReadableMap center) { if (center != null) { double lat = center.getDouble("lat"); double lon = center.getDouble("lon"); // ... парсинг, проверки, обработка ошибок } } }
Новый ViewManager на Kotlin с Codegen:
@ReactModule(name = NAME) class YaMapViewManager : SimpleViewManager<YaMapView>(), YaMapViewManagerInterface<YaMapView> { // ← Codegen интерфейс override fun setInitialRegion(view: YaMapView, value: ReadableMap?) { // Типы гарантированы Codegen view.setInitialRegion(value) } }
4. Переходим к итерациям. Код компилируется, но что-то не работает на устройстве — допустим, из-за несоответствия имени события. Codegen требует, чтобы на Android они именовались строго как top+ PascalCase, а у нас написано markerPress вместо topMarkerPress — и событие просто не приходит в JS. Без ИИ-шки поиск проблемы занял бы у нас часы, а с ней — пять минут.
Около четырёх часов диалога — и у нас есть рабочая библиотека. Откуда знаем, что рабочая?
Тестовое приложение
Юнит-тесты тут особо не помогут: нужно запускать на реальном устройстве, тыкать в карту руками и смотреть, что происходит. Проверять на реальном приложении и смотреть, не сломается ли что-нибудь уже в проде, тоже не самая классная идея.
Поэтому параллельно с библиотекой мы написали тестовое приложение как под Android, так и под iOS, которое покрывает все основные фичи и компоненты, которые используются в Яндекс Картах: Marker, Circle, Polygon, Polyline, кластеры. Пишем функцию — и сразу проверяем на живом устройстве.
Как бонус, теперь это приложение работает как страховка на будущее. Когда Яндекс выпустит новый SDK (а это случается регулярно), мы просто обновим библиотеку, запустим аппку и сразу увидим, что изменилось или сломалось.
Что в итоге-то
Библиотека полностью поддерживает новую архитектуру React Native Fabric и TurboModules, строгую типизацию через Codegen и последний SDK от Яндекса. Кода стало меньше, функциональности больше, архитектура чище.
Из компонентов есть карта, маркеры, полигоны, полилинии, круги, кластеризация маркеров. Из функционала — поиск, геоподсказки, геокодирование, построение маршрутов.
Сам Claude суммировал так:

Всем, кто боится, что ИИ отнимет у нас работу, пора напрячься: нейронка уже даже оценивает сроки как реальный разработчик. По факту, конечно, две недели у нас бы ушло без Claude — с ним всё заняло два дня.
Мораль
Если >50% библиотеки нужно переписывать, лучше начать с нуля. В этом случае старый код — уже не фундамент, а якорь: без него может получиться быстрее, проще и чище. Особенно с нейронкой, которая сэкономила нам минимум 8 рабочих дней.
Как и сказали в начале, либу для Яндекс Карт пока что в опенсорс выкладывать не будем. Но у нас есть другие:
react-native-device-country — определяет местоположение устройства по SIM-карте, без GPS (только на Android);
react-native-wallet-manager — даёт доступ к базовым функциям Apple Wallet: добавление и удаление пассивов, проверка карт и билетов на существование (подробнее рассказывали тут);
next-pwa-pack делает из любого Next.js-проекта полноценное PWA одной строкой (тоже делились статьёй на тему).
С вопросами, предложениями и «вот у нас такая же штука была» ждём в комментариях. Ну и подписывайтесь, конечно — если передумаем и решим делиться библиотекой, то напишем сюда.
