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

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

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

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

  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 の両方のプラットフォームで作業する必要があります。

注意

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

これで、Web ビューをレンダリングできるように 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 イベントの考えられる結果です。
  • 実際の OnScriptLoadedEvent は React Native の Event クラスを拡張する必要があります。

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 関数をオーバーライドします。これは、仕様の codegenNativeComponent 関数呼び出しで使用されるのと同じ名前を返す必要があります。

createViewInstance 関数は、新しい ReactWebView をインスタンス化する責任があります。

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

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

4. ReactWebViewPackage を記述する

ネイティブモジュールと同様に、ネイティブコンポーネントも 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 がレンダリングする必要があるビューに応じて適切な ViewManager を返します。
  • getReactModuleInfoProvider は、モジュールをランタイムに登録するときに必要なすべての情報を提供します。

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

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

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