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

ネイティブコンポーネント

Android の独自の CheckBox や iOS の UIButton のような Host Component をラップする新しい React Native コンポーネントを構築したい場合は、Fabric Native Component を使用する必要があります。

このガイドでは、Web ビュー コンポーネントを実装することで、Fabric Native Component の構築方法を説明します。その手順は次のとおりです。

  1. Flow または TypeScript を使用して JavaScript 仕様を定義します。
  2. 提供された仕様からコードを生成し、自動リンクされるように依存関係管理システムを設定します。
  3. ネイティブ コードを実装します。
  4. アプリケーションでコンポーネントを使用します。

コンポーネントを使用するには、プレーンテンプレートで生成されたアプリケーションが必要です。

bash
npx @react-native-community/cli@latest init Demo --install-pods false

WebView コンポーネントを作成する

このガイドでは、Web View コンポーネントの作成方法を説明します。Android の WebView コンポーネントと iOS の WKWebView コンポーネントを使用して、コンポーネントを作成します。

まず、コンポーネントのコードを格納するフォルダー構造を作成しましょう。

bash
mkdir -p Demo/{specs,android/app/src/main/java/com/webview}

これにより、作業する以下のレイアウトが得られます。

Demo
├── android/app/src/main/java/com/webview
└── ios
└── specs
  • android/app/src/main/java/com/webview フォルダーは、Android コードを格納するフォルダーです。
  • ios フォルダーは、iOS コードを格納するフォルダーです。
  • specs フォルダーは、Codegen の仕様ファイルを格納するフォルダーです。

1. Codegen の仕様を定義する

仕様は、TypeScript または Flow のいずれかで定義する必要があります(詳細については Codegen のドキュメントを参照してください)。これは Codegen によって使用され、C++、Objective-C++、および Java を生成して、プラットフォーム コードを React が実行される JavaScript ランタイムに接続します。

仕様ファイルは、Codegen で動作するように <MODULE_NAME>NativeComponent.{ts|js} という名前である必要があります。サフィックス NativeComponent は単なる慣例ではなく、実際に Codegen によって仕様ファイルを検出するために使用されます。

WebView コンポーネントにこの仕様を使用します。

Demo/specs/WebViewNativeComponent.ts
import type {
CodegenTypes,
HostComponent,
ViewProps,
} from 'react-native';
import {codegenNativeComponent} from 'react-native';

type WebViewScriptLoadedEvent = {
result: 'success' | 'error';
};

export interface NativeProps extends ViewProps {
sourceURL?: string;
onScriptLoaded?: CodegenTypes.BubblingEventHandler<WebViewScriptLoadedEvent> | null;
}

export default codegenNativeComponent<NativeProps>(
'CustomWebView',
) as HostComponent<NativeProps>;

この仕様は、インポートを除いて、3つの主要な部分で構成されています。

  • WebViewScriptLoadedEvent は、イベントがネイティブから JavaScript に渡す必要のあるデータをサポートするデータ型です。
  • NativeProps は、コンポーネントに設定できるプロパティの定義です。
  • codegenNativeComponent ステートメントにより、カスタム コンポーネントのコードを自動生成でき、ネイティブ実装と一致するために使用されるコンポーネントの名前を定義します。

Native Modules と同様に、specs/ ディレクトリに複数の仕様ファイルを含めることができます。使用できる型と、これらがマップするプラットフォームの型に関する詳細については、付録を参照してください。

2. Codegen を実行するように構成する

この仕様は、React Native の Codegen ツールによって使用され、プラットフォーム固有のインターフェースとボイラープレートを生成します。これを行うには、Codegen は仕様を見つける場所と、それを使って何をすべきかを知る必要があります。package.json を更新して、以下を含めます。

json
    "start": "react-native start",
"test": "jest"
},
"codegenConfig": {
"name": "AppSpec",
"type": "components",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.webview"
},
"ios": {
"componentProvider": {
"CustomWebView": "RCTWebView"
}
}
},
"dependencies": {

Codegen のすべての設定が完了したら、生成されたコードにフックするためにネイティブ コードを準備する必要があります。

iOS の場合、仕様によってエクスポートされた JS コンポーネントの名前 (CustomWebView) と、コンポーネントをネイティブに実装する iOS クラスを宣言的にマッピングしていることに注意してください。

2. ネイティブコードのビルド

これでネイティブプラットフォームコードを記述する時間です。これにより、React がビューをレンダリングする必要があるときに、プラットフォームが正しいネイティブビューを作成し、画面にレンダリングできるようになります。

Android と iOS の両方のプラットフォームで作業する必要があります。

このガイドでは、新しいアーキテクチャのみで動作するネイティブコンポーネントを作成する方法を示します。新しいアーキテクチャと従来のアーキテクチャの両方をサポートする必要がある場合は、後方互換性ガイドを参照してください。

これで、ウェブビューをレンダリングできるように、Android プラットフォームコードをいくつか記述する時間です。従う必要がある手順は次のとおりです。

  • Codegen の実行
  • ReactWebView のコードを記述する
  • ReactWebViewManager のコードを記述する
  • ReactWebViewPackage のコードを記述する
  • アプリケーションに ReactWebViewPackage を登録する

1. Gradle を介して Codegen を実行する

選択した IDE が使用できるボイラープレートを生成するために、これを一度実行します。

Demo/
cd android
./gradlew generateCodegenArtifactsFromSchema

Codegen は、実装する必要がある ViewManager インターフェースと、Web ビューの ViewManager デリゲートを生成します。

2. ReactWebView を記述する

ReactWebView は、カスタム コンポーネントを使用するときに React Native がレンダリングする Android ネイティブ ビューをラップするコンポーネントです。

android/src/main/java/com/webview フォルダーに、このコードを含む ReactWebView.java または ReactWebView.kt ファイルを作成します。

Demo/android/src/main/java/com/webview/ReactWebView.java
package com.webview;

import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.events.Event;

public class ReactWebView extends WebView {
public ReactWebView(Context context) {
super(context);
configureComponent();
}

public ReactWebView(Context context, AttributeSet attrs) {
super(context, attrs);
configureComponent();
}

public ReactWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
configureComponent();
}

private void configureComponent() {
this.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
this.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
emitOnScriptLoaded(OnScriptLoadedEventResult.success);
}
});
}

public void emitOnScriptLoaded(OnScriptLoadedEventResult result) {
ReactContext reactContext = (ReactContext) context;
int surfaceId = UIManagerHelper.getSurfaceId(reactContext);
EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, getId());
WritableMap payload = Arguments.createMap();
payload.putString("result", result.name());

OnScriptLoadedEvent event = new OnScriptLoadedEvent(surfaceId, getId(), payload);
if (eventDispatcher != null) {
eventDispatcher.dispatchEvent(event);
}
}

public enum OnScriptLoadedEventResult {
success,
error
}

private class OnScriptLoadedEvent extends Event<OnScriptLoadedEvent> {
private final WritableMap payload;

OnScriptLoadedEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}

@Override
public String getEventName() {
return "onScriptLoaded";
}

@Override
public WritableMap getEventData() {
return payload;
}
}
}

ReactWebView は Android の WebView を拡張しているため、プラットフォームで既に定義されているすべてのプロパティを簡単に再利用できます。

このクラスは、3つの Android コンストラクタを定義していますが、実際の実装はプライベートな configureComponent 関数に委ねています。この関数は、コンポーネント固有のすべてのプロパティを初期化します。この場合、WebView のレイアウトを設定し、WebView の動作をカスタマイズするために使用する WebClient を定義しています。このコードでは、ReactWebViewWebClientonPageFinished メソッドを実装することで、ページの読み込みが完了したときにイベントを発行します。

コードは、実際にイベントを発行するためのヘルパー関数を定義しています。イベントを発行するには、次の手順を実行する必要があります。

  • ReactContext への参照を取得します。
  • 表示しているビューの surfaceId を取得します。
  • ビューに関連付けられた eventDispatcher への参照を取得します。
  • WritableMap オブジェクトを使用してイベントのペイロードを作成します。
  • JavaScript に送信する必要があるイベント オブジェクトを作成します。
  • eventDispatcher.dispatchEvent を呼び出してイベントを送信します。

ファイルの最後の部分は、イベントを送信するために必要なデータ型の定義を含んでいます。

  • OnScriptLoadedEventResult は、OnScriptLoaded イベントの考えられる結果です。
  • React Native の Event クラスを拡張する必要がある実際の OnScriptLoadedEvent です。

3. WebViewManager を記述する

WebViewManager は、React Native ランタイムをネイティブ ビューに接続するクラスです。

React は、特定のコンポーネントをレンダリングするようアプリから指示を受け取ると、登録されたビューマネージャーを使用してビューを作成し、必要なすべてのプロパティを渡します。

これが ReactWebViewManager のコードです。

Demo/android/src/main/java/com/webview/ReactWebViewManager.java
package com.webview;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.viewmanagers.CustomWebViewManagerInterface;
import com.facebook.react.viewmanagers.CustomWebViewManagerDelegate;

import java.util.HashMap;
import java.util.Map;

@ReactModule(name = ReactWebViewManager.REACT_CLASS)
class ReactWebViewManager extends SimpleViewManager<ReactWebView> implements CustomWebViewManagerInterface<ReactWebView> {
private final CustomWebViewManagerDelegate<ReactWebView, ReactWebViewManager> delegate =
new CustomWebViewManagerDelegate<>(this);

@Override
public ViewManagerDelegate<ReactWebView> getDelegate() {
return delegate;
}

@Override
public String getName() {
return REACT_CLASS;
}

@Override
public ReactWebView createViewInstance(ThemedReactContext context) {
return new ReactWebView(context);
}

@ReactProp(name = "sourceUrl")
@Override
public void setSourceURL(ReactWebView view, String sourceURL) {
if (sourceURL == null) {
view.emitOnScriptLoaded(ReactWebView.OnScriptLoadedEventResult.error);
return;
}
view.loadUrl(sourceURL, new HashMap<>());
}

public static final String REACT_CLASS = "CustomWebView";

@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
Map<String, Object> map = new HashMap<>();
Map<String, Object> bubblingMap = new HashMap<>();
bubblingMap.put("phasedRegistrationNames", new HashMap<String, String>() {{
put("bubbled", "onScriptLoaded");
put("captured", "onScriptLoadedCapture");
}});
map.put("onScriptLoaded", bubblingMap);
return map;
}
}

ReactWebViewManager は React の SimpleViewManager クラスを拡張し、Codegen によって生成された CustomWebViewManagerInterface を実装します。

Codegen によって生成された別の要素である CustomWebViewManagerDelegate の参照を保持します。

次に、getName 関数をオーバーライドします。これは、spec の codegenNativeComponent 関数呼び出しで使用されたのと同じ名前を返す必要があります。

createViewInstance 関数は、新しい ReactWebView をインスタンス化する役割を担います。

次に、ViewManager は、React のコンポーネントのすべてのプロパティがネイティブ ビューをどのように更新するかを定義する必要があります。この例では、React が WebView に設定する sourceURL プロパティをどのように処理するかを決定する必要があります。

最後に、コンポーネントがイベントを発行できる場合は、バブリング イベントの場合は getExportedCustomBubblingEventTypeConstants をオーバーライドし、直接イベントの場合は getExportedCustomDirectEventTypeConstants をオーバーライドして、イベント名をマッピングする必要があります。

4. ReactWebViewPackage を記述する

Native Modules と同様に、Native Components も ReactPackage クラスを実装する必要があります。これは、React Native ランタイムにコンポーネントを登録するために使用できるオブジェクトです。

これが ReactWebViewPackage のコードです。

Demo/android/src/main/java/com/webview/ReactWebViewPackage.java
package com.webview;

import com.facebook.react.BaseReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.uimanager.ViewManager;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ReactWebViewPackage extends BaseReactPackage {
@Override
public List<ViewManager<?, ?>> createViewManagers(ReactApplicationContext reactContext) {
return Collections.singletonList(new ReactWebViewManager(reactContext));
}

@Override
public NativeModule getModule(String s, ReactApplicationContext reactApplicationContext) {
if (ReactWebViewManager.REACT_CLASS.equals(s)) {
return new ReactWebViewManager(reactApplicationContext);
}
return null;
}

@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
return new ReactModuleInfoProvider() {
@Override
public Map<String, ReactModuleInfo> getReactModuleInfos() {
Map<String, ReactModuleInfo> map = new HashMap<>();
map.put(ReactWebViewManager.REACT_CLASS, new ReactModuleInfo(
ReactWebViewManager.REACT_CLASS, // name
ReactWebViewManager.REACT_CLASS, // className
false, // canOverrideExistingModule
false, // needsEagerInit
false, // isCxxModule
true // isTurboModule
));
return map;
}
};
}
}

ReactWebViewPackageBaseReactPackage を拡張し、コンポーネントを適切に登録するために必要なすべてのメソッドを実装します。

  • createViewManagers メソッドは、カスタム ビューを管理する ViewManager を作成するファクトリ メソッドです。
  • getModule メソッドは、React Native がレンダリングする必要がある View に応じて適切な ViewManager を返します。
  • getReactModuleInfoProvider は、ランタイムにモジュールを登録するときに必要なすべての情報を提供します。

5. アプリケーションに ReactWebViewPackage を登録する

最後に、アプリケーションに ReactWebViewPackage を登録する必要があります。これは、MainApplication ファイルを修正し、getPackages 関数によって返されるパッケージのリストに ReactWebViewPackage を追加することで行います。

Demo/app/src/main/java/com/demo/MainApplication.kt
package com.demo

import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.react.soloader.OpenSourceMergedSoMapping
import com.facebook.soloader.SoLoader
import com.webview.ReactWebViewPackage

class MainApplication : Application(), ReactApplication {

override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
add(ReactWebViewPackage())
}

override fun getJSMainModuleName(): String = "index"

override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG

override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
}

override val reactHost: ReactHost
get() = getDefaultReactHost(applicationContext, reactNativeHost)

override fun onCreate() {
super.onCreate()
SoLoader.init(this, OpenSourceMergedSoMapping)
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
load()
}
}
}

3. ネイティブコンポーネントを使用する

最後に、新しいコンポーネントをアプリで使用できます。生成された App.tsx を次のように更新します。

Demo/App.tsx
import React from 'react';
import {Alert, StyleSheet, View} from 'react-native';
import WebView from './specs/WebViewNativeComponent';

function App(): React.JSX.Element {
return (
<View style={styles.container}>
<WebView
sourceURL="https://react.dokyumento.jp/"
style={styles.webview}
onScriptLoaded={() => {
Alert.alert('Page Loaded');
}}
/>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
alignContent: 'center',
},
webview: {
width: '100%',
height: '100%',
},
});

export default App;

このコードは、作成した新しい WebView コンポーネントを使用して react.dev ウェブサイトを読み込むアプリを作成します。

アプリは、ウェブページが読み込まれたときにもアラートを表示します。

4. WebView コンポーネントを使用してアプリを実行する

bash
yarn run android
AndroidiOS