Introduzione ai moduli nativi nel React Native

React Native consente agli sviluppatori di costruire applicazioni mobili multipiattaforma utilizzando JavaScript e React. Mentre le navi di framework con un ricco insieme di componenti integrati per elementi comuni dell'interfaccia utente e API del dispositivo di base, le applicazioni di produzione spesso richiedono funzionalità al di là di runtime di default di React Native.

In questa guida, passeremo attraverso l'intero ciclo di vita di un modulo nativo – dall'impostazione del codice della piattaforma di ambiente e scrittura, alla registrazione del modulo, alla visualizzazione dei metodi e al consumo di JavaScript.

Comprendere i moduli nativi e il ponte

Il core, l’architettura di React Native si basa su un ponte che serializza e deserializza i messaggi tra il thread JavaScript e i thread nativi. I moduli nativi sono oggetti che vivono sul lato nativo e sono registrati con il runtime JavaScript. Quando si chiama un metodo su un modulo nativo di JS, il ponte invia un messaggio al lato nativo, esegue il metodo, e restituisce il risultato (se c'è) indietro a JS.

I moduli nativi sono particolarmente preziosi nei seguenti scenari:

  • È necessario integrare una libreria nativa (ad esempio, per l'elaborazione dei pagamenti, l'apprendimento automatico o la realtà aumentata) che non ha wrapper JavaScript.
  • È necessario eseguire operazioni ad alte prestazioni e a bassa latenza che sarebbero rallentate dal thread JS (ad esempio, elaborazione delle immagini, sintesi audio).
  • È necessario accedere a funzionalità specifiche della piattaforma che non fanno parte del core di React Native – come il iOS HomeKit o il Prompt Biometrico di Android.
  • Si desidera condividere il codice tra un React Native app e un codebase nativo esistente.

Prima di immergersi in implementazione, è importante capire che i moduli nativi non sono un “proiettile magico” – aumentano la complessità, richiedono il test su ogni piattaforma e possono introdurre problemi di sicurezza del thread. Tuttavia, quando utilizzati in modo magistrale, consentono il tipo di funzionalità avanzata che distingue la tua app dai concorrenti più semplici cross-platform.

Impostazione del tuo ambiente di sviluppo

Per creare moduli nativi, è necessario lo standard React Native ambiente di sviluppo sia per Android che per iOS. Questo include Java/Kotlin tooling per Android (Android Studio, Gradle, Android SDK) e Xcode per iOS con CocoaPods (o Swift Package Manager) per la gestione della dipendenza. Assicurarsi di aver scritto React Native Code prima di tentare moduli nativi – si dovrebbe essere a proprio agio con i progetti base di creare un React Native.

La maggior parte degli sviluppatori inizia con un progetto React Native creato tramite []. All’interno del progetto, i moduli nativi sono generalmente collocati all’interno delle directory [ e ]].Per modularità e riutilizzabilità, è possibile anche avvolgere il modulo nativo come pacchetto npm separato – molti moduli comunitari seguono questo modello.

Creazione di un modulo nativo: I principi fondamentali

Ogni modulo nativo è composto da due parti: l'implementazione specifica della piattaforma (una per Android, una per iOS) e il codice di consumo JavaScript.

Android (Kotlin/Java)

Su Android, si crea una classe che si estende ] e implementa i metodi che si desidera esporre. Ogni metodo che dovrebbe essere chiamato da JavaScript deve essere annotato con [] e avere un tipo di ritorno (o utilizzare un callback / promessa per restituire i dati).

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)
 }
 }
}

Dopo aver definito il modulo, è necessario registrarlo con il sistema di pacchetto React Native. Creare una classe di pacchetti che implementa :

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()
 }
}

Infine, aggiungete il pacchetto al vostro ] (o ]) all'interno del metodo :

override fun getPackages(): List<ReactPackage> {
 val packages = PackageList(this).packages
 packages.add(MyNativePackage())
 return packages
}

iOS (Swift/Objective‐C)

Su iOS, i moduli nativi sono tipicamente scritti in Objective‐C o Swift. React Native si aspetta un file di intestazione e l'implementazione che si conformi al protocollo []. Per Swift, è necessario creare un intestazione di collegamento.

// 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

Non è necessario alcun pacchetto aggiuntivo di registrazione per iOS – React Native scopre automaticamente le classi che si conformano a . Tuttavia, è necessario garantire che il file .m sia compilato nel vostro obiettivo e che si esegue se si aggiungono eventuali dipendenze native.

Per Swift, i passaggi sono simili ma richiedono l'attributo e un intestazione di spicco.

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)
 }
}

Ricordatevi di importare nella vostra testata di spicco Obiettivo-C: .

Consumare moduli nativi da JavaScript

Una volta che il modulo nativo viene implementato e registrato, è possibile accedervi nel codice JavaScript React Native tramite l'oggetto . Il nome che fornisci (Android) o (iOS) diventa la chiave sotto la quale appare il modulo.

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));

Si può anche utilizzare o gestori di eventi come necessario. Nota che il nome del metodo in JavaScript deve corrispondere esattamente al nome del metodo esportato nel codice nativo.

Per i metodi che accettano più parametri, semplicemente passarli come argomenti – il ponte si occuperà della serializzazione. I tipi di parametri supportati includono , , , , ]] (mappa), e funzioni (callbacks). Tuttavia, utilizzando Promises tramite l'argomento

Modelli di comunicazione avanzati

Mentre le chiamate di base coprono molti casi di utilizzo, le caratteristiche avanzate richiedono spesso modelli di comunicazione più sofisticati.

Richiami contro Promessi

Per le operazioni di una volta, le promesse sono le più pulite. Per i flussi di dati continui (ad esempio, le letture dei sensori), si può preferire callback. Tuttavia, i callback sono meno comuni nel moderno React Native perché possono portare a callback-hell. Un modello migliore per i flussi è quello di emettere eventi dal lato nativo a JavaScript tramite il (iOS) o F]

Emettere eventi da Nativo a JavaScript

Per inviare dati in modo asincrono dal codice nativo a JS (ad esempio, quando un sensore nativo aggiorna), è possibile utilizzare l'emettitore singolo. Su Android, ottenere un riferimento al e chiamare :

reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
 .emit("onSensorUpdate", Arguments.createMap().apply {
 putString("data", sensorValue)
 })

Su iOS, importa e sovrascrive gli eventi supportati:

@implementation MyNativeModule

RCT_EXPORT_MODULE();

- (NSArray<NSString *> *)supportedEvents {
 return @[@"onSensorUpdate"];
}

// In your native method or delegate callback:
[self sendEventWithName:@"onSensorUpdate" body:@{@"data": sensorValue}];
@end

In JavaScript, iscrivetevi all'evento utilizzando :

import { NativeEventEmitter, NativeModules } from 'react-native';

const { MyNativeModule } = NativeModules;
const eventEmitter = new NativeEventEmitter(MyNativeModule);

eventEmitter.addListener('onSensorUpdate', (event) => {
 console.log('Sensor data:', event.data);
});

Non dimenticare di rimuovere gli ascoltatori quando il componente smonta per evitare perdite di memoria.

Gestione e prestazioni del thread

Per impostazione predefinita, tutte le chiamate del modulo nativo vengono eseguite sul pool filettato nativo, separato dal thread UI. Questo è bene per la maggior parte delle operazioni, ma se è necessario aggiornare l'interfaccia utente direttamente dal modulo nativo (ad esempio, eseguire un calcolo pesante e aggiornare una vista), è necessario inviare il lavoro al thread principale. Su Android, utilizzare ; su iOS, utilizzare .

Per le attività di sfondo a lungo termine (come la sincronizzazione del database o la scansione Bluetooth), considerare l'utilizzo o i callback per evitare il blocco del ponte. È inoltre possibile deporre fili aggiuntivi all'interno del modulo nativo, ma essere cauti con sicurezza del thread – lo stato condiviso deve essere sincronizzato.

Punta di prestazione: minimizzare le dimensioni dei dati passati attraverso il ponte. Grandi array o blobs binari (ad esempio, immagini) devono essere gestiti con percorsi di file o riferimenti piuttosto che serializzati in JSON.

Esempi reali del mondo

Per solidificare i concetti, esaminiamo due casi di uso comune in cui i moduli nativi sono indispensabili.

Accesso alla macchina fotografica

React Native ha un built-in ma non include un API fotocamera di basso livello. Un modulo nativo può avvolgere la fotocamera della piattaforma – per esempio, Android API o iOS ]. Il modulo potrebbe esporre i metodi per aprire l'anteprima della fotocamera, catturare una foto e restituire il percorso di immagine a JS. Questo è esattamente ciò che le libredi libreriererie flash popolari come il manuale [F

Comunicazione a bassa energia Bluetooth (BLE)

Collegamento alle periferiche BLE (ad esempio, monitor di frequenza cardiaca, beacon) richiede API di sistema nativo. Un modulo nativo può eseguire la scansione per i dispositivi, collegare, scoprire i servizi e leggere / scrivere caratteristiche. Perché le operazioni BLE sono asincroni e spesso comportano callback, emettendo eventi (come o )) è il modello naturale.

Migliori Pratiche e Pitfalls

  • Test di unità di scrittura per il codice nativo. Sia Android Studio e Xcode forniscono framework di prova (JUnit, XCTest).
  • ]Le autorizzazioni di Handle con grazia. Molti moduli nativi richiedono autorizzazioni di runtime (camera, posizione, Bluetooth). React Native funziona per Android, ma per iOS è necessario richiedere autorizzazioni all'interno del modulo nativo o utilizzare una libreria come . Non assumere mai le autorizzazioni sono concesse; gestire il rifiuto.
  • Tenere sotto controllo la luce del ponte.[] Evitare di passare spesso oggetti di grandi dimensioni. Se è necessario trasmettere dati ad alta frequenza (ad esempio, sensore a 100Hz), prendere in considerazione aggiornamenti di batching o passare a un modello in cui i dati pre-processi di codice nativo e solo invia risultati aggregati.
  • Documenta l'API del tuo modulo nativo a fondo. Usa JSDoc o un README per descrivere ogni metodo, i suoi parametri, i valori di ritorno e le differenze della piattaforma. Questo è fondamentale quando altri membri del team o la comunità open source consumano il modulo.
  • Utilizza solo dopo l'applicazione è inizializzata. Poiché i moduli nativi si affidano al ponte essendo pronti, accedendo troppo presto (ad esempio, in un contesto globale) possono portare a .
  • Leva i moduli esistenti quando possibile. L'ecosistema React Native ha migliaia di pacchetti. Prima di scrivere il proprio modulo nativo, cerca quelli esistenti. Se trovi un modulo che si adatta quasi alle tue esigenze, consideri contribuire piuttosto che reinventare la ruota.
  • Consider TurboModules per nuovi progetti. A partire da React Native 0.68, la nuova architettura con TurboModules offre prestazioni migliori e condivisione di memoria JavaScript-native. Se si sta avviando una nuova app, valutare utilizzando TurboModules (via la Nuova Architettura) invece del vecchio ponte.

Risorse esterne

Per approfondire lo sviluppo dei moduli nativi, consultare l’organizzazione ufficiale React Documentazione nativa sui moduli nativi]. Per una guida specifica della piattaforma, Android developmenter guide e La documentazione dello sviluppatore di Apple fornisce riferimenti autorevoli.

Conclusioni

I moduli nativi sono uno strumento potente nell’arsenale dello sviluppatore React Native, che consente l’accesso a tutta la larghezza delle funzionalità della piattaforma. Mentre introducono una maggiore complessità – richiedendo la conoscenza di Java/Kotlin o Swift/Objective‐C, un’attenta gestione dei thread e un rigoroso test – il payoff è la capacità di fornire funzionalità avanzate che altrimenti sarebbero impossibili.

Iniziare piccolo: costruire un semplice modulo di calcolatrice che moltiplica due numeri e restituisce il risultato. Poi laurearsi a moduli più complessi come un indicatore di livello della batteria o un filtro di immagine personalizzato. Ogni modulo nativo si crea approfondisce la vostra comprensione della piattaforma e ti rende uno sviluppatore mobile più versatile.