React NativeでモバイルOSのすべての機能を使用する方法

市場には、Cordova、Xamarin、React Nativeなど、あまり知られていないクロスプラットフォームソリューションがいくつかあります。 多くのモバイル開発者は、クロスプラットフォームソリューションでは、ネイティブアプリケーションでできることを実行できないと考えています。


記事では、この神話を解き明かし、React Nativeのメカニズムについて説明します。これにより、ネイティブアプリケーションで可能なすべてのことを実行できます。 このメカニズムはネイティブモジュールです。 Under the cut-AndroidおよびiOS用のネイティブモジュールの作成方法の詳細な説明。


画像


携帯電話向けのクロスプラットフォーム開発のネイティブモジュールは、いくつかのことを支援します。



React Nativeのサンプルアプリケーション図


オペレーティングシステムはネイティブアプリケーションを実行します。 React Nativeランタイムと、アプリケーション開発者(またはReact Nativeのライブラリの作成者)によって作成されたネイティブモジュールのコードは、低レベルで動作します。 その上で、React Native Bridgeは機能します-ネイティブコードとjsの間の中間リンク。 Js自体はJavaScript VM内で実行されるJS VM内で実行されます。 iOSでは、システムによって提供され、Androidでは、アプリケーションはそれをライブラリの形式でドラッグします。


画像


ネイティブモジュールの作成


Androidの下で


計画はこれです:



桁1-Androidコンポーネント


主な背景がAndroidの場合、このリトリートをスキップできます。 基本的なiOSまたはReact JSの経験を持つ開発者の場合、Androidアプリケーションに次のコンポーネントを含めることができることを確認する必要があります。



このコンテキスト(khe-khe)では、もちろん、アプリケーションにのみ興味があります。 このコンポーネントはアプリケーション自体のオブジェクトであることを思い出させてください。 React Nativeアプリケーションの場合は、アプリケーションクラスを実装し、このクラスを使用してReactApplicationインターフェイスを実装できます(およびReact Nativeアプリケーションの場合)。


package com.facebook.react; public interface ReactApplication { ReactNativeHost getReactNativeHost(); } 

これは、ReactNativeが使用したいネイティブパッケージについて学習するために必要です。 これを行うには、アプリケーションはパッケージリストをリストするReactNativeHostのインスタンスを返す必要があります。


 class MainApplication : Application(), ReactApplication { private val mReactNativeHost = object : ReactNativeHost(this) { override fun getPackages(): List<ReactPackage> { return Arrays.asList( MainReactPackage(), NativeLoggerPackage() ) } override fun getReactNativeHost(): ReactNativeHost { return mReactNativeHost } } 

NativeLoggerPackageは、お客様と一緒に作成するパッケージです。 渡された値のみを記録し、実際の機能ではなくネイティブモジュールの作成プロセスに焦点を合わせます。


アプリケーションがReactApplicationを実装する必要があるのはなぜですか? React Nativeの中にはこんな面白いコードがあるからです:


 public class ReactActivityDelegate { protected ReactNativeHost getReactNativeHost() { return ((ReactApplication) getPlainActivity().getApplication()) .getReactNativeHost(); } } 

画像


次に、NativeLoggerPackageを実装します。


 class NativeLoggerPackage : ReactPackage { override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> { return Arrays.asList<NativeModule>(NativeLoggerModule()) } override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> { return emptyList<ViewManager<*, *>>() } } 

createViewManagersメソッドは省略していますが、この記事では重要ではありません。 また、作成されたモジュールのリストを返すcreateNativeModulesメソッドが重要です。 モジュールは、jsから呼び出すことができるメソッドを含むクラスです。 NativeLoggerModuleを作成しましょう:


 class NativeLoggerModule : BaseJavaModule() { override fun getName(): String { return "NativeLogger" } } 

Androidコンテキストにアクセスする必要がない場合は、少なくともBaseJavaModuleからモジュールを継承する必要があります。 必要がある場合は、別の基本クラスを使用する必要があります。


 class NativeLoggerModule(context : ReactApplicationContext) : ReactContextBaseJavaModule(context) { override fun getName(): String { return "NativeLogger" } } 

いずれの場合でも、getName()メソッドを定義する必要があります。このメソッドは、jsでモジュールを使用できる名前を返します。これについては後で説明します。
最後に、jsのメソッドを作成しましょう。 これは、ReactMethodアノテーションを使用して行われます。


 class NativeLoggerModule : BaseJavaModule() { override fun getName(): String { return "NativeLogger" } @ReactMethod fun logTheObject() { Log.d(name, “Method called”) } } 

ここで、jsからの呼び出しにlogTheObjectメソッドが使用可能になります。 しかし、何も返さないパラメータなしでメソッドを呼び出したいだけではありません。 引数を扱いましょう(左側にjavaタイプ、右側にjs):


ブール->ブール
整数->数値
ダブル->数字
フロート->数値
文字列->文字列
コールバック->関数
ReadableMap->オブジェクト
ReadableArray->配列


jsオブジェクトをネイティブメソッドに渡したいとします。 ReadableMapはjavaに含まれます。


 @ReactMethod fun logTheObject(map: ReadableMap) { val value = map.getString("key1") Log.d(name, "key1 = " + value) } 

配列の場合、ReadableArrayが渡され、反復処理が問題になりません。


 @ReactMethod fun logTheArray(array: ReadableArray) { val size = array.size() for (index in 0 until size) { val value = array.getInt(index) Log.d(name, "array[$index] = $value") } } 

ただし、最初の引数でオブジェクトを渡し、2番目の引数で配列を渡したい場合は、ここでも驚くことはありません。


 @ReactMethod fun logTheMapAndArray(map: ReadableMap, array: ReadableArray): Boolean { logTheObject(map) logTheArray(array) return true } 

これをjavascriptから呼び出すにはどうすればよいですか? これ以上簡単なことはありません。 最初に行うことは、react-nativeルートライブラリからNativeModulesをインポートすることです。


 import { NativeModules } from 'react-native'; 

そして、モジュールをインポートします(NativeLoggerと呼びましたか?):


 import { NativeModules } from 'react-native'; const nativeModule = NativeModules.NativeLogger; 

これでメソッドを呼び出すことができます:


 import { NativeModules } from 'react-native'; const nativeModule = NativeModules.NativeLogger; export const log = () => { nativeModule.logTheMapAndArray( { key1: 'value1' }, ['1', '2', '3'] ); }; 

うまくいく! しかし、待ってください、私はすべてが正常であるかどうか、記録したいものを記録できたかどうかを知りたいです。 戻り値はどうですか?


画像


ただし、ネイティブモジュールからの関数の戻り値はありません。 関数を渡して外に出なければなりません:


 @ReactMethod fun logWithCallback(map: ReadableMap, array: ReadableArray, callback: Callback) { logTheObject(map) logTheArray(array) callback.invoke("Logged") } 

唯一のinvokeメソッド(Object ... args)を持つCallbackインターフェイスは、ネイティブコードに入ります。 js側から見ると、これは単なる関数です。


 import { NativeModules } from 'react-native'; const nativeModule = NativeModules.NativeLogger; export const log = () => { const result = nativeModule.logWithCallback( { key1: 'value1' }, [1, 2, 3], (message) => { console.log(`[NativeLogger] message = ${message}`) } ); }; 

残念ながら、コンパイル時にjsのネイティブコードと関数からコールバックパラメーターを検証するツールはありません。注意してください。


幸いなことに、Promisesメカニズムを使用できます。これは、ネイティブコードではPromiseインターフェイスでサポートされています。


 @ReactMethod fun logAsync(value: String, promise: Promise) { Log.d(name, "Logging value: " + value) promise.resolve("Promise done") } 

その後、async / awaitを使用してこのコードを呼び出すことができます。


 import { NativeModules } from 'react-native'; const nativeModule = NativeModules.NativeLogger; export const log = async () => { const result = await nativeModule.logAsync('Logged value'); console.log(`[NativeModule] results = ${result}`); }; 

これで、Androidのjsでネイティブメソッドを設定する作業が完了しました。 iOSを見てください。


画像


iOSでネイティブモジュールを作成する


最初に、NativeLogger.hモジュールを作成します。


 #import <Foundation/Foundation.h> #import <React/RCTBridgeModule.h> @interface NativeLogger : NSObject<RCTBridgeModule> @end 

NativeLogger.mの実装:


 #import <Foundation/Foundation.h> #import "NativeLogger.h" @implementation NativeLogger { } RCT_EXPORT_MODULE(); 

RCT_EXPORT_MODULEは、モジュールが宣言されているファイルの名前でReactNativeにモジュールを登録するマクロです。 js内のこの名前があまり適していない場合は、変更できます。


 @implementation NativeLogger { } RCT_EXPORT_MODULE(NativeLogger); 

それでは、Androidに対して行ったメソッドを実装しましょう。 これにはパラメーターが必要です。


 string -> (NSString*) number -> (NSInteger*, float, double, CGFloat*, NSNumber*) boolean -> (BOOL, NSNumber*) array -> (NSArray*) object -> (NSDictionary*) function -> (RCTResponseSenderBlock) 

メソッドを宣言するには、RCT_EXPORT_METHODマクロを使用できます。


 RCT_EXPORT_METHOD(logTheObject:(NSDictionary*) map) { NSString *value = map[@"key1"]; NSLog(@"[NativeModule] %@", value); } 

 RCT_EXPORT_METHOD(logTheArray:(NSArray*) array) { for (id record in array) { NSLog(@"[NativeModule] %@", record); } } 

 RCT_EXPORT_METHOD(log:(NSDictionary*) map withArray:(NSArray*)array andCallback:(RCTResponseSenderBlock)block) { NSLog(@"Got the log"); NSArray* events = @[@"Logged"]; block(@[[NSNull null], events]); } 

ここで最も興味深いのは、約束のサポートです。 これを行うには、別のマクロRCT_REMAP_METHODを使用する必要があります。このマクロは、jsのメソッド名を最初の引数として使用し、objective-cのメソッドシグネチャを2番目以降の引数として使用します。
インターフェイスの代わりに、2つの引数が渡されます。約束を解決するためのRCTPromiseResolveBlockと拒否のためのRCTPromiseRejectBlockです。


 RCT_REMAP_METHOD(logAsync, logAsyncWith:(NSString*)value withResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { NSLog(@"[NativeModule] %@", value); NSArray* events = @[@"Logged"]; resolve(events); } 

以上です。 イベントをネイティブモジュールからjsに送信するメカニズムについては、別の記事で説明します。


ニュアンス



便利なリンク




Source: https://habr.com/ru/post/J347346/


All Articles