メインコンテンツにスキップ

ネイティブとReact Native間の通信

既存アプリとの統合ガイドネイティブUIコンポーネントガイドで、React Nativeをネイティブコンポーネントに、またその逆を埋め込む方法を学びます。ネイティブとReact Nativeコンポーネントを混在させると、最終的にこれら2つの世界間で通信する必要が出てきます。これを実現するためのいくつかの方法は、他のガイドですでに言及されています。この記事では、利用可能な手法をまとめます。

はじめに

React NativeはReactに触発されているため、情報フローの基本的な考え方は似ています。Reactのフローは一方向です。各コンポーネントが親とその内部状態にのみ依存するコンポーネントの階層を維持します。これはプロパティを使用して行います。データは、親から子へトップダウン方式で渡されます。先祖コンポーネントが子孫の状態に依存する場合、先祖を更新するために子孫が使用するコールバックを渡す必要があります。

同じ概念がReact Nativeにも適用されます。アプリケーションをフレームワーク内のみで構築している限り、プロパティとコールバックを使用してアプリを駆動できます。しかし、React Nativeとネイティブコンポーネントを混在させる場合は、それらの間で情報を渡すことができる特定のクロス言語メカニズムが必要になります。

プロパティ

プロパティは、コンポーネント間の通信の最も簡単な方法です。そのため、ネイティブからReact Nativeへ、またReact Nativeからネイティブへの両方にプロパティを渡す方法が必要です。

ネイティブからReact Nativeにプロパティを渡す

メインアクティビティでReactActivityDelegateのカスタム実装を提供することにより、React Nativeアプリにプロパティを渡すことができます。この実装では、getLaunchOptionsをオーバーライドして、必要なプロパティを持つBundleを返す必要があります。

public class MainActivity extends ReactActivity {
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
@Override
protected Bundle getLaunchOptions() {
Bundle initialProperties = new Bundle();
ArrayList<String> imageList = new ArrayList<String>(Arrays.asList(
"https://dummyimage.com/600x400/ffffff/000000.png",
"https://dummyimage.com/600x400/000000/ffffff.png"
));
initialProperties.putStringArrayList("images", imageList);
return initialProperties;
}
};
}
}
import React from 'react';
import {View, Image} from 'react-native';

export default class ImageBrowserApp extends React.Component {
renderImage(imgURI) {
return <Image source={{uri: imgURI}} />;
}
render() {
return <View>{this.props.images.map(this.renderImage)}</View>;
}
}

ReactRootViewは、読み書き可能なプロパティappPropertiesを提供します。appPropertiesが設定されると、React Nativeアプリは新しいプロパティで再レンダリングされます。更新は、新しい更新されたプロパティが以前のプロパティと異なる場合にのみ実行されます。

Bundle updatedProps = mReactRootView.getAppProperties();
ArrayList<String> imageList = new ArrayList<String>(Arrays.asList(
"https://dummyimage.com/600x400/ff0000/000000.png",
"https://dummyimage.com/600x400/ffffff/ff0000.png"
));
updatedProps.putStringArrayList("images", imageList);

mReactRootView.setAppProperties(updatedProps);

プロパティはいつでも更新してもかまいません。ただし、更新はメインスレッドで実行する必要があります。getterはどのスレッドでも使用できます。

一度にいくつかのプロパティのみを更新する方法はありません。代わりに、独自のラッパーに組み込むことをお勧めします。

注意: 現在、最上位のRNコンポーネントのJS関数componentWillUpdatePropsは、propの更新後には呼び出されません。ただし、componentDidMount関数で新しいpropsにアクセスできます。

React Nativeからネイティブにプロパティを渡す

ネイティブコンポーネントのプロパティを公開する問題は、この記事で詳しく説明されています。簡単に言うと、JavaScriptに反映されるプロパティは、@ReactPropで注釈が付けられたsetterメソッドとして公開する必要があり、その後、コンポーネントが通常のReact NativeコンポーネントであるかのようにReact Nativeで使用します。

プロパティの制限

クロス言語プロパティの主な欠点は、ボトムアップのデータバインディングを処理できるコールバックをサポートしていないことです。JSアクションの結果としてネイティブの親ビューから削除したい小さなRNビューがあると想像してください。情報はボトムアップに移動する必要があるため、プロップを使用してそれを行う方法はありません。

クロス言語コールバック (ここに記載) のフレーバーはありますが、これらのコールバックは必ずしも必要なものではありません。主な問題は、プロパティとして渡すことを意図していないことです。むしろ、このメカニズムを使用すると、JSからネイティブアクションをトリガーし、そのアクションの結果をJSで処理できます。

クロス言語インタラクションのその他の方法 (イベントとネイティブモジュール)

前の章で述べたように、プロパティの使用にはいくつかの制限があります。場合によっては、プロパティだけではアプリのロジックを駆動するのに十分ではなく、より柔軟性のあるソリューションが必要になります。この章では、React Nativeで利用できるその他の通信手法について説明します。これらは、内部通信 (RNのJSレイヤーとネイティブレイヤー間) と、外部通信 (RNとアプリの「純粋なネイティブ」部分間) の両方に使用できます。

React Nativeを使用すると、クロス言語関数呼び出しを実行できます。JSからカスタムネイティブコードを実行でき、その逆も可能です。残念ながら、作業している側によっては、同じ目標を異なる方法で達成します。ネイティブの場合、イベントメカニズムを使用してJSでハンドラー関数の実行をスケジュールしますが、React Nativeの場合、ネイティブモジュールによってエクスポートされたメソッドを直接呼び出します。

ネイティブからReact Native関数を呼び出す (イベント)

イベントについては、この記事で詳しく説明しています。イベントは別のスレッドで処理されるため、イベントの使用は実行時間に関する保証を提供しないことに注意してください。

イベントは、React Nativeコンポーネントへの参照を必要とせずに、React Nativeコンポーネントを変更できるため強力です。ただし、イベントの使用中に陥る可能性のあるいくつかの落とし穴があります。

  • イベントはどこからでも送信できるため、プロジェクトにスパゲッティスタイルの依存関係が生じる可能性があります。
  • イベントは名前空間を共有するため、名前の衝突が発生する可能性があります。衝突は静的に検出されないため、デバッグが困難になります。
  • 同じReact Nativeコンポーネントの複数のインスタンスを使用し、イベントの観点からそれらを区別したい場合は、識別子を導入し、イベントと一緒に渡す必要がある可能性が高くなります (ネイティブビューのreactTagを識別子として使用できます)。

React Nativeからネイティブ関数を呼び出す (ネイティブモジュール)

ネイティブモジュールは、JSで使用できるJava/Kotlinクラスです。通常、各モジュールの1つのインスタンスがJSブリッジごとに作成されます。それらは、任意の関数と定数をReact Nativeにエクスポートできます。これらについては、この記事で詳しく説明しています。

警告: すべてのネイティブモジュールは同じ名前空間を共有します。新しいモジュールを作成するときは、名前の衝突に注意してください。