Урок по треку React Native 7 мин

Нативные модули: TurboModules, Codegen, Expo Modules

TurboModules и Codegen, нативные компоненты, Expo Modules API, события из натива.

Рано или поздно в RN-проекте нужен нативный код: SDK банка, своя камера, датчик, фоновая работа. На middle и senior собесах просят рассказать, как связать JS и натив, и что изменилось с новой архитектурой.

Когда нужен свой модуль

Сначала ищут готовую библиотеку: экосистема большая, у Expo есть модули почти на всё. Свой модуль пишут, когда нужно встроить закрытый SDK, нужна производительность (обработка изображений, криптография) или платформенная функция, которой нет в библиотеках.

TurboModule: путь с Codegen

  1. Спецификация на TypeScript: интерфейс модуля.
// NativeBattery.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  getLevel(): Promise<number>;
  isLowPowerMode(): boolean; // синхронный метод
}
export default TurboModuleRegistry.getEnforcing<Spec>('NativeBattery');
  1. Codegen по спецификации генерирует интерфейсы на C++, Kotlin/Java и Objective-C++ и склейку с JSI. Конфигурация в package.json (codegenConfig), генерация запускается при сборке.
  2. Реализация на Kotlin и Objective-C/Swift: класс реализует сгенерированный интерфейс и регистрируется в пакете.
  3. Вызов из JS обычной функцией: await NativeBattery.getLevel().

Что изменилось по сравнению со старыми модулями: модуль грузится лениво при первом обращении, вызов идёт через JSI без JSON-сериализации, типы JS и натива сверяются при сборке. Синхронные методы стали возможны, но злоупотреблять ими нельзя: синхронный вызов блокирует JS-поток на время работы натива.

Типы на границе

Codegen поддерживает примитивы (number, string, boolean), объекты с описанной формой, массивы, Promise, колбэки, enum. Большие бинарные данные гонять через границу всё равно дорого: файлы передают путями, а изображения обрабатывают на нативной стороне.

События из натива

Нативный код может отправлять события в JS: смена состояния SDK, прогресс загрузки. В спецификации TurboModule объявляют EventEmitter, на JS-стороне подписка возвращает объект с remove(). Подписку обязательно снимают в очистке useEffect, иначе обработчик держит компонент и вызывается после размонтирования.

Нативные компоненты

Свой нативный UI (карта, плеер, график на нативе) оформляют как Fabric-компонент: спецификация пропсов на TypeScript, Codegen, нативная реализация View. Пропсы типизированы, команды к компоненту (например, play()) описываются отдельно.

Expo Modules API

Альтернатива для тех, кто не хочет писать C++-склейку. Модуль описывается DSL на Swift и Kotlin:

class BatteryModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("Battery")
    AsyncFunction("getLevel") { readLevel() }
    Events("onLowPower")
  }
}

Expo Modules работают поверх JSI, поддерживают новую архитектуру и работают не только в Expo-проектах, но и в bare-приложениях после установки expo. Для многих команд это самый быстрый путь к своему модулю.

Потоки

Нативный метод не должен блокировать главный поток платформы долгой работой: обработку уводят в фоновый поток (корутины, DispatchQueue), а результат возвращают через промис. Обновление нативного UI, наоборот, делают на главном потоке.

Частые ошибки

  • Писать модуль, когда есть готовая поддерживаемая библиотека.
  • Синхронные методы для долгих операций.
  • Передавать большие данные через границу вместо путей к файлам.
  • Не снимать подписку на нативные события.
  • Нет обработки ошибок: отклонять промис с понятным кодом.

Что спросят на собесе

  • Как сделать нативный модуль в новой архитектуре: шаги.
  • Что такое Codegen и что он проверяет.
  • Чем TurboModule отличается от старого модуля.
  • Как отправить событие из натива в JS.
  • Expo Modules API: когда выбрать его.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.