Как использовать React Native с родными модулями для расширенных функций
Введение в родные модули в React Native
React Native дает разработчикам возможность создавать кроссплатформенные мобильные приложения с использованием JavaScript и React. В то время как фреймворк поставляется с богатым набором встроенных компонентов для общих элементов пользовательского интерфейса и API-интерфейсов основных устройств, производственные приложения часто требуют возможностей, выходящих за рамки среды выполнения JavaScript по умолчанию React Native. Нативные модули служат мостом между JavaScript и платформенным кодом, написанным на Swift, Objective-C, Kotlin или Java. Они открывают доступ к аппаратным датчикам, системным службам, сторонним SDK и функциям, которые в противном случае было бы трудно или невозможно реализовать с помощью чистого JavaScript. Понимание того, как создавать и интегрировать нативные модули, имеет важное значение для создания передовых, многофункциональных приложений, которые действительно нативны как на iOS, так и на Android.
В этом руководстве мы рассмотрим весь жизненный цикл родного модуля - от настройки вашей среды и написания кода платформы до регистрации модуля, раскрытия методов и потребления их из JavaScript. Мы также рассмотрим лучшие практики, соображения производительности и реальные примеры, такие как доступ к камере, обработка фоновых задач и общение между потоками. К концу у вас будет прочная основа для расширения возможностей React Native с помощью собственных родных модулей.
Понимание родных модулей и моста
По своей сути, архитектура React Native опирается на мост, который сериализует и десериализирует сообщения между потоком JavaScript и нативными потоками. Нативные модули - это объекты, которые живут на нативной стороне и зарегистрированы во время выполнения JavaScript. Когда вы вызываете метод на нативной стороне из JS, мост отправляет сообщение на нативную сторону, выполняет метод и возвращает результат (если таковой имеется) обратно в JS. Этот шаблон позволяет использовать системные API - например, доступ к гироскопу устройства, чтение тегов NFC или шифрование данных с использованием безопасного анклава платформы - которые не подвергаются воздействию через стандартные библиотеки React Native.
Нативные модули особенно ценны в следующих сценариях:
- Вам нужно интегрировать нативную библиотеку (например, для обработки платежей, машинного обучения или дополненной реальности), которая не имеет обертки JavaScript.
- Вам нужны высокопроизводительные операции с низкой задержкой, которые будут замедляться потоком JS (например, обработка изображений, синтез аудио).
- Вы должны получить доступ к функциям платформы, которые не являются частью ядра React Native, например, к iOS HomeKit или к биометрической скорости Android.
- Вы хотите поделиться кодом между приложением React Native и существующей исходной кодовой базой.
Прежде чем погрузиться в реализацию, важно понять, что нативные модули не являются «волшебной пулей» - они увеличивают сложность, требуют тестирования на каждой платформе и могут вводить проблемы безопасности потоков. Однако при разумном использовании они позволяют использовать расширенные функции, которые отличают ваше приложение от более простых кроссплатформенных конкурентов.
Создайте свою среду развития
Для создания нативных модулей вам нужна стандартная среда разработки React Native для Android и iOS. Это включает в себя инструментарий Java/Kotlin для Android (Android Studio, Gradle, Android SDK) и Xcode для iOS с CocoaPods (или Swift Package Manager) для управления зависимостью. Убедитесь, что вы написали код React Native перед попыткой нативных модулей - вам следует быть комфортно с основами создания проекта React Native и понимания структуры файла.
Большинство разработчиков начинают с проекта React Native, созданного с помощью . Внутри проекта нативные модули обычно размещаются в каталогах и . Для модульности и многоразового использования вы также можете обернуть свой родной модуль в отдельный пакет npm - многие модули сообщества следуют этому шаблону. Мы сосредоточимся на подходе in-project для ясности.
Создание родного модуля: основы
Каждый нативный модуль состоит из двух частей: реализация платформы (одна для Android, другая для iOS) и код потребления JavaScript.
Android (Kotlin/Java)
На Android вы создаете класс, который расширяет и реализует методы, которые вы хотите раскрыть. Каждый метод, который должен быть вызван с JavaScript, должен быть аннотирован и иметь тип возврата (или использовать обратный вызов / обещание для возврата данных). Вот минимальный пример в Kotlin:
package com.yourapp
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise
class MyNativeModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
override fun getName(): String = "MyNativeModule"
@ReactMethod
fun doSomething(param: String, promise: Promise) {
try {
// Perform native work here
val result = "Hello from Kotlin! Received: $param"
promise.resolve(result)
} catch (e: Exception) {
promise.reject("ERROR", e.message)
}
}
}
После определения модуля необходимо зарегистрировать его в системе пакетов React Native. Создайте класс пакетов, который реализует :
package com.yourapp
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class MyNativePackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(MyNativeModule(reactContext))
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
}
В конце концов, добавьте пакет в свой (или ) метод :
override fun getPackages(): List<ReactPackage> {
val packages = PackageList(this).packages
packages.add(MyNativePackage())
return packages
}
iOS (Swift/Objective-C)
На iOS нативные модули обычно пишутся в Objective-C или Swift. React Native ожидает файл заголовка и реализацию, которая соответствует протоколу . Для Swift необходимо создать перемычку заголовка. Вот пример Objective-C:
// MyNativeModule.h
#import <React/RCTBridgeModule.h>
@interface MyNativeModule : NSObject <RCTBridgeModule>
@end
// MyNativeModule.m
#import "MyNativeModule.h"
#import <React/RCTLog.h>
@implementation MyNativeModule
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(doSomething:(NSString *)param
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSString *result = [NSString stringWithFormat:@"Hello from iOS! Received: %@", param];
resolve(result);
}
@end
Для iOS не требуется дополнительная регистрация пакетов - React Native автоматически обнаруживает классы, которые соответствуют . Однако вы должны убедиться, что файл .m скомпилирован в вашей цели и что вы запустите , если вы добавите какие-либо нативные зависимости.
Для Swift шаги похожи, но требуют атрибута и перемычки заголовка. Типичная реализация Swift выглядит следующим образом:
import Foundation
@objc(MyNativeModule)
class MyNativeModule: NSObject {
@objc
func doSomething(_ param: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
let result = "Hello from Swift! Received: \(param)"
resolve(result)
}
}
Не забудьте импортировать в заголовке Objective-C: .
Потребление родных модулей из JavaScript
После того, как ваш родной модуль реализован и зарегистрирован, вы можете получить доступ к нему в своем коде React Native JavaScript через объект . Имя, которое вы предоставляете в (Android) или (iOS), становится ключом, под которым появляется модуль.
import { NativeModules } from 'react-native';
const { MyNativeModule } = NativeModules;
// Calling an exported method
MyNativeModule.doSomething('React Native')
.then(result => console.log(result))
.catch(error => console.error(error));
Вы также можете использовать или обработчики событий по мере необходимости. Обратите внимание, что имя метода в JavaScript должно точно соответствовать имени метода, которое вы экспортировали в нативном коде. React Native отображает их 1:1, игнорируя параметр имени аннотации метода Android, если вы его переопределите.
Для методов, которые принимают несколько параметров, просто передайте их в качестве аргументов - мост будет обрабатывать сериализацию. Поддерживаемые типы параметров включают , , , , (карты) и функции (обратные вызовы). Однако использование Promises через аргумент (Android) или блоки резолятора / режектора (iOS) является рекомендуемым подходом, поскольку он чисто интегрируется с асинхронной природой React Native.
Расширенные коммуникационные шаблоны
В то время как базовые вызовы методов охватывают многие варианты использования, расширенные функции часто требуют более сложных моделей связи. React Native поддерживает несколько механизмов:
Обещания vs. обратные вызовы
Для одноразовых операций Обещания являются самыми чистыми. Для непрерывных потоков данных (например, показаний датчиков) вы можете предпочесть обратные вызовы. Однако обратные вызовы менее распространены в современном React Native, потому что они могут привести к обратному вызову. Лучшая картина для потоков - излучать события с родной стороны на JavaScript через (iOS) или и (Android).
Изменяя события от Native до JavaScript
Для асинхронной отправки данных из нативного кода в JS (например, при обновлении нативного датчика) можно использовать одиночную систему излучателей событий. На Android получить ссылку на и позвонить :
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit("onSensorUpdate", Arguments.createMap().apply {
putString("data", sensorValue)
})
На iOS импорт и отмена поддерживаемых событий:
@implementation MyNativeModule
RCT_EXPORT_MODULE();
- (NSArray<NSString *> *)supportedEvents {
return @[@"onSensorUpdate"];
}
// In your native method or delegate callback:
[self sendEventWithName:@"onSensorUpdate" body:@{@"data": sensorValue}];
@end
В JavaScript подпишитесь на событие, используя :
import { NativeEventEmitter, NativeModules } from 'react-native';
const { MyNativeModule } = NativeModules;
const eventEmitter = new NativeEventEmitter(MyNativeModule);
eventEmitter.addListener('onSensorUpdate', (event) => {
console.log('Sensor data:', event.data);
});
Не забудьте удалить слушателей, когда компонент не установлен, чтобы избежать утечки памяти.
Управление потоком и производительность
По умолчанию все вызовы нативного модуля выполняются на нативном пуле потоков, отдельном от потока пользовательского интерфейса. Это хорошо для большинства операций, но если вам нужно обновить пользовательский интерфейс непосредственно из вашего родного модуля (например, выполняя тяжелые вычисления и обновляя вид), вы должны отправить работу на основной поток. На Android используйте ; на iOS используйте .
Для долгосрочных задач фона (таких как синхронизация базы данных или сканирование Bluetooth) рассмотрите возможность использования или обратных вызовов, чтобы избежать блокировки моста. Вы также можете создавать дополнительные потоки внутри своего родного модуля, но будьте осторожны с безопасностью потока - общее состояние должно быть синхронизировано.
Наконечник производительности: минимизировать размер данных, передаваемых через мост. Большие массивы или двоичные сгустки (например, изображения) должны обрабатываться с помощью файловых путей или ссылок, а не сериализоваться в JSON.
Реальные примеры
Чтобы закрепить концепции, давайте рассмотрим два распространенных варианта использования, где нативные модули незаменимы.
Камера Доступ
React Native имеет встроенный , но не включает в себя API камеры низкого уровня. Нативный модуль может обернуть камеру платформы - например, API Android или iOS . Ваш модуль может подвергать методы открытию предварительного просмотра камеры, захвату фотографии и возвращению пути изображения в JS. Это именно то, что делают популярные библиотеки, такие как .
Bluetooth Low Energy (BLE) - низкоэнергетическая связь
Подключение к периферийным устройствам BLE (например, мониторам сердечного ритма, маякам) требует нативных системных API. Нативный модуль может сканировать устройства, подключаться, обнаруживать службы и характеристики чтения / записи. Поскольку операции BLE асинхронны и часто включают обратный вызов, испускание событий (например, или ) является естественным шаблоном. Существует несколько модулей BLE с открытым исходным кодом, но создание собственных гарантирует, что вы только обнаружите функциональность, необходимую вашему приложению, и дает вам контроль над резьбой.
Лучшие практики и подводные камни
- Напишите модульные тесты для нативного кода. И Android Studio, и Xcode предоставляют рамки тестирования (JUnit, XCTest). Проверьте методы вашего модуля изолированно, прежде чем интегрироваться с React Native.
- Разрешения на обработку изящно. Многие модули Native требуют разрешений на время выполнения (камера, местоположение, Bluetooth). React Native работает для Android, но для iOS вам может потребоваться запросить разрешения из вашего родного модуля или использовать библиотеку, такую как . Никогда не думайте, что разрешения предоставляются; обработайте отказ.
- Сохраняйте свет моста. Избегайте частого пропускания больших объектов. Если вам нужно передавать высокочастотные данные (например, датчик на 100 Гц), рассмотрите возможность пакетирования обновлений или перехода на модель, где нативный код предварительно обрабатывает данные и отправляет только агрегированные результаты.
- Документируйте API вашего родного модуля тщательно. Используйте JSDoc или README для описания каждого метода, его параметров, значений возврата и различий в платформе. Это имеет решающее значение, когда другие члены команды или сообщество с открытым исходным кодом потребляют ваш модуль.
- Использовать только после инициализации приложения. Поскольку нативные модули полагаются на то, что мост готов, доступ к ним слишком рано (например, в глобальном контексте) может привести к . Всегда получайте доступ к ним внутри компонента или после , который ждет мост.
- Используйте существующие модули, когда это возможно. Экосистема React Native имеет тысячи пакетов. Перед написанием собственного родного модуля ищите существующие. Если вы найдете модуль, который почти соответствует вашим потребностям, подумайте о том, чтобы внести свой вклад, а не заново изобретать колесо.
- Рассматривайте TurboModules для новых проектов. Начиная с React Native 0.68, новая архитектура с TurboModules предлагает лучшую производительность и обмен памятью на JavaScript. Если вы запускаете новое приложение, оцените использование TurboModules (через новую архитектуру) вместо старого моста.
Внешние ресурсы
Чтобы глубже погрузиться в разработку родных модулей, обратитесь к официальной документации React Native по родным модулям . Для руководства по платформе руководства для разработчиков Android и Документация для разработчиков Apple предоставляют авторитетные ссылки. Кроме того, поддерживаемая сообществом организация React Native Community GitHub содержит много хорошо протестированных родных модулей, которые вы можете изучать в качестве примеров.
Заключение
Нативные модули являются мощным инструментом в арсенале разработчика React Native, позволяя получить доступ к полному спектру возможностей платформы. В то время как они вводят дополнительную сложность - требующую знания Java / Kotlin или Swift / Objective-C, тщательного управления потоками и строгого тестирования - выгода заключается в возможности предоставлять расширенные функции, которые в противном случае были бы невозможны. Следуя шаблонам, изложенным в этом руководстве - создавая хорошо структурированный нативный модуль, используя обещания и события для чистой асинхронной связи и придерживаясь лучших практик вокруг разрешений и производительности - вы можете уверенно расширить React Native за пределы его встроенных границ.
Начните с малого: создайте простой калькуляторный модуль, который умножает два числа и возвращает результат. Затем переходите к более сложным модулям, таким как индикатор уровня батареи или пользовательский фильтр изображения. Каждый создаваемый вами нативный модуль углубляет ваше понимание платформы и делает вас более универсальным мобильным разработчиком.