ネイティブコンポーネント
AndroidのCheckBox
やiOSのUIButton
のようなホストコンポーネントをラップする*新しい* React Nativeコンポーネントを構築する場合は、Fabric Nativeコンポーネントを使用する必要があります。
このガイドでは、Webビューコンポーネントを実装することで、Fabric Nativeコンポーネントを構築する方法を説明します。手順は以下のとおりです。
- FlowまたはTypeScriptを使用してJavaScript仕様を定義します。
- 依存関係管理システムを設定して、提供された仕様からコードを生成し、自動的にリンクされるようにします。
- ネイティブコードを実装します。
- アプリでコンポーネントを使用します。
コンポーネントを使用するには、プレーンテンプレートで生成されたアプリケーションが必要です。
npx @react-native-community/cli@latest init Demo --install-pods false
WebViewコンポーネントの作成
このガイドでは、Webビューコンポーネントを作成する方法を説明します。AndroidのWebView
コンポーネントとiOSのWKWebView
コンポーネントを使用してコンポーネントを作成します。
まず、コンポーネントのコードを格納するフォルダ構造を作成しましょう。
mkdir -p Demo/{specs,android/app/src/main/java/com/webview}
これにより、作業を行う次のレイアウトが得られます。
Demo
├── android/app/src/main/java/com/webview
└── ios
└── spec
android/app/src/main/java/com/webview
フォルダは、Androidコードを格納するフォルダです。ios
フォルダは、iOSコードを格納するフォルダです。spec
フォルダは、コード生成の仕様ファイルを格納するフォルダです。
1. コード生成の仕様を定義する
仕様は、TypeScriptまたはFlowで定義する必要があります(詳細については、コード生成のドキュメントを参照してください)。これは、コード生成によって、プラットフォームコードをReactが実行されるJavaScriptランタイムに接続するためのC++、Objective-C++、およびJavaを生成するために使用されます。
仕様ファイルは、コード生成で機能するように、<MODULE_NAME>NativeComponent.{ts|js}
という名前である必要があります。接尾辞NativeComponent
は単なる規則ではなく、実際にコード生成によって仕様ファイルを検出するために使用されます。
WebViewコンポーネントには、この仕様を使用します。
- TypeScript
- Flow
import type {HostComponent, ViewProps} from 'react-native';
import type {BubblingEventHandler} from 'react-native/Libraries/Types/CodegenTypes';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
type WebViewScriptLoadedEvent = {
result: 'success' | 'error';
};
export interface NativeProps extends ViewProps {
sourceURL?: string;
onScriptLoaded?: BubblingEventHandler<WebViewScriptLoadedEvent> | null;
}
export default codegenNativeComponent<NativeProps>(
'CustomWebView',
) as HostComponent<NativeProps>;
// @flow strict-local
import type {HostComponent, ViewProps} from 'react-native';
import type {BubblingEventHandler} from 'react-native/Libraries/Types/CodegenTypes';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
type WebViewScriptLoadedEvent = $ReadOnly<{|
result: "success" | "error",
|}>;
type NativeProps = $ReadOnly<{|
...ViewProps,
sourceURL?: string;
onScriptLoaded?: BubblingEventHandler<WebViewScriptLoadedEvent>?;
|}>;
export default (codegenNativeComponent<NativeProps>(
'CustomWebView',
): HostComponent<NativeProps>);
この仕様は、インポートを除いて、3つの主要部分で構成されています。
WebViewScriptLoadedEvent
は、イベントがネイティブからJavaScriptに渡す必要があるデータのサポートデータ型です。NativeProps
は、コンポーネントに設定できるプロパティの定義です。codegenNativeComponent
ステートメントは、カスタムコンポーネントのコードをコード生成することを可能にし、ネイティブ実装と一致するために使用されるコンポーネントの名前を定義します。
ネイティブモジュールと同様に、specs/
ディレクトリに複数の仕様ファイルを含めることができます。使用できる型と、これらがマップされるプラットフォーム型の詳細については、付録を参照してください。
2. コード生成を実行するように構成する
この仕様は、React Nativeのコード生成ツールによって、プラットフォーム固有のインターフェースとボイラープレートを生成するために使用されます。これを行うために、コード生成は、仕様がどこにあるか、そしてそれで何をする必要があるかを知る必要があります。package.json
を更新して、以下を含めます。
"start": "react-native start",
"test": "jest"
},
"codegenConfig": {
"name": "AppSpec",
"type": "components",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.nativelocalstorage"
}
},
"dependencies": {
コード生成のすべてが配線されたので、生成されたコードにフックするようにネイティブコードを準備する必要があります。
2. ネイティブコードの構築
Reactがビューのレンダリングを要求したときに、プラットフォームが適切なネイティブビューを作成して画面にレンダリングできるように、ネイティブプラットフォームコードを記述します。
AndroidとiOSの両方のプラットフォームで作業する必要があります。
このガイドでは、新しいアーキテクチャでのみ機能するネイティブコンポーネントの作成方法を示しています。新しいアーキテクチャとレガシーアーキテクチャの両方をサポートする必要がある場合は、下位互換性ガイドを参照してください。
- Android
- iOS
Webビューをレンダリングできるように、Androidプラットフォームコードを記述します。以下の手順に従う必要があります。
- コード生成の実行
ReactWebView
のコードを記述するReactWebViewManager
のコードを記述するReactWebViewPackage
のコードを記述する- アプリケーションに
ReactWebViewPackage
を登録する
1. Gradleを介してコード生成を実行する
これを実行して、選択したIDEが使用できるボイラープレートを生成します。
cd android
./gradlew generateCodegenArtifactsFromSchema
コード生成は、実装する必要があるViewManager
インターフェースと、WebビューのViewManager
デリゲートを生成します。
2. ReactWebView
を記述する
ReactWebView
は、React Nativeがカスタムコンポーネントを使用するときにレンダリングするAndroidネイティブビューをラップするコンポーネントです。
android/src/main/java/com/webview
フォルダに、このコードを含むReactWebView.java
ファイルまたはReactWebView.kt
ファイルを作成します。
- Java
- Kotlin
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;
}
}
}
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
class ReactWebView: WebView {
constructor(context: Context) : super(context) {
configureComponent()
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
configureComponent()
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
configureComponent()
}
private fun configureComponent() {
this.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
this.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
emitOnScriptLoaded(OnScriptLoadedEventResult.success)
}
}
}
fun emitOnScriptLoaded(result: OnScriptLoadedEventResult) {
val reactContext = context as ReactContext
val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
val payload =
Arguments.createMap().apply {
putString("result", result.name)
}
val event = OnScriptLoadedEvent(surfaceId, id, payload)
eventDispatcher?.dispatchEvent(event)
}
enum class OnScriptLoadedEventResult() {
success(),
error()
}
inner class OnScriptLoadedEvent(
surfaceId: Int,
viewId: Int,
private val payload: WritableMap
) : Event<OnScriptLoadedEvent>(surfaceId, viewId) {
override fun getEventName() = "onScriptLoaded"
override fun getEventData() = payload
}
}
ReactWebView
はAndroid WebView
を拡張するため、プラットフォームによって既に定義されているすべてのプロパティを簡単に再利用できます。
クラスは3つのAndroidコンストラクタを定義しますが、実際の実装はプライベートなconfigureComponent
関数に委任します。この関数は、すべてのコンポーネント固有のプロパティの初期化を処理します。この場合、WebView
のレイアウトを設定し、WebView
の動作をカスタマイズするために使用するWebClient
を定義しています。このコードでは、ReactWebView
は、WebClient
のonPageFinished
メソッドを実装することで、ページの読み込みが完了したときにイベントを発行します。
次に、コードはイベントを実際に発行するためのヘルパー関数を定義します。イベントを発行するには、次の手順を実行する必要があります。
ReactContext
への参照を取得します。- 表示しているビューの
surfaceId
を取得します。 - ビューに関連付けられた
eventDispatcher
への参照を取得します。 WritableMap
オブジェクトを使用して、イベントのペイロードを作成します。- JavaScriptに送信する必要があるイベントオブジェクトを作成します。
eventDispatcher.dispatchEvent
を呼び出して、イベントを送信します。
ファイルの最後の部分には、イベントを送信するために必要なデータ型の定義が含まれています。
OnScriptLoaded
イベントの考えられる結果を含むOnScriptLoadedEventResult
。- React Nativeの
Event
クラスを拡張する必要がある実際のOnScriptLoadedEvent
。
3. WebViewManager
を記述する
WebViewManager
は、React Nativeランタイムとネイティブビューを接続するクラスです。
Reactがアプリから特定のコンポーネントをレンダリングする命令を受け取ると、Reactは登録されたビューマネージャーを使用してビューを作成し、必要なすべてのプロパティを渡します。
これは、ReactWebViewManager
のコードです。
- Java
- Kotlin
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;
}
}
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;
@ReactModule(name = ReactWebViewManager.REACT_CLASS)
class ReactWebViewManager(context: ReactApplicationContext) : SimpleViewManager<ReactWebView>(), CustomWebViewManagerInterface<ReactWebView> {
private val delegate: CustomWebViewManagerDelegate<ReactWebView, ReactWebViewManager> =
CustomWebViewManagerDelegate(this)
override fun getDelegate(): ViewManagerDelegate<ReactWebView> = delegate
override fun getName(): String = REACT_CLASS
override fun createViewInstance(context: ThemedReactContext): ReactWebView = ReactWebView(context)
@ReactProp(name = "sourceUrl")
override fun setSourceURL(view: ReactWebView, sourceURL: String?) {
if (sourceURL == null) {
view.emitOnScriptLoaded(ReactWebView.OnScriptLoadedEventResult.error)
return;
}
view.loadUrl(sourceURL, emptyMap())
}
companion object {
const val REACT_CLASS = "CustomWebView"
}
override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Any> =
mapOf(
"onScriptLoaded" to
mapOf(
"phasedRegistrationNames" to
mapOf(
"bubbled" to "onScriptLoaded",
"captured" to "onScriptLoadedCapture"
)))
}
ReactWebViewManager
は、React の SimpleViewManager
クラスを拡張し、Codegen によって生成された CustomWebViewManagerInterface
を実装します。
これは、Codegen によって生成された別の要素である CustomWebViewManagerDelegate
への参照を保持します。
次に、getName
関数をオーバーライドします。この関数は、仕様の codegenNativeComponent
関数呼び出しで使用されるのと同じ名前を返す必要があります。
createViewInstance
関数は、新しい ReactWebView
をインスタンス化する役割を担います。
次に、ViewManager は、React のコンポーネントのすべての props がネイティブビューをどのように更新するかを定義する必要があります。この例では、React が WebView
に設定する sourceURL
プロパティをどのように処理するかを決定する必要があります。
最後に、コンポーネントがイベントを発行できる場合、バブリングイベントの場合は getExportedCustomBubblingEventTypeConstants
を、直接イベントの場合は getExportedCustomDirectEventTypeConstants
をオーバーライドして、イベント名をマッピングする必要があります。
4. ReactWebViewPackage
を記述する
ネイティブモジュールと同様に、ネイティブコンポーネントも ReactPackage
クラスを実装する必要があります。これは、React Native ランタイムにコンポーネントを登録するために使用できるオブジェクトです。
これは ReactWebViewPackage
のコードです。
- Java
- Kotlin
package com.webview;
import com.facebook.react.TurboReactPackage;
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 TurboReactPackage {
@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> get() {
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;
}
};
}
}
package com.webview
import com.facebook.react.TurboReactPackage
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
class ReactWebViewPackage : TurboReactPackage() {
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return listOf(ReactWebViewManager(reactContext))
}
override fun getModule(s: String, reactApplicationContext: ReactApplicationContext): NativeModule? {
when (s) {
ReactWebViewManager.REACT_CLASS -> ReactWebViewManager(reactApplicationContext)
}
return null
}
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider {
mapOf(ReactWebViewManager.REACT_CLASS to ReactModuleInfo(
_name = ReactWebViewManager.REACT_CLASS,
_className = ReactWebViewManager.REACT_CLASS,
_canOverrideExistingModule = false,
_needsEagerInit = false,
isCxxModule = false,
isTurboModule = true,
)
)
}
}
ReactWebViewPackage
は TurboReactPackage
を拡張し、コンポーネントを適切に登録するために必要なすべてのメソッドを実装します。
createViewManagers
メソッドは、カスタムビューを管理するViewManager
を作成するファクトリメソッドです。getModule
メソッドは、React Native がレンダリングする必要がある View に応じて、適切な ViewManager を返します。getReactModuleInfoProvider
は、ランタイムにモジュールを登録するときに必要なすべての情報を提供します。
5. アプリケーションに ReactWebViewPackage
を登録する
最後に、アプリケーションに ReactWebViewPackage
を登録する必要があります。そのためには、MainApplication
ファイルを変更し、getPackages
関数によって返されるパッケージのリストに ReactWebViewPackage
を追加します。
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()
}
}
}
Web ビューをレンダリングできるようにするために、iOS プラットフォームコードを記述します。以下の手順に従う必要があります。
- Codegen を実行します。
RCTWebView
のコードを記述します。- アプリケーションに
RCTWebView
を登録します。
1. Codegen を実行する
Codegen を手動で実行できますが、コンポーネントのデモを行うアプリケーションを使用してこれを行う方が簡単です。
cd ios
bundle install
bundle exec pods install
重要なのは、Codegen からのログ出力が表示されることです。これを Xcode で使用して、WebView ネイティブコンポーネントをビルドします。
生成されたコードをリポジトリにコミットすることには注意が必要です。生成されたコードは、React Native の各バージョンに固有です。npm peerDependencies を使用して、React Native のバージョンとの互換性を制限してください。
3. RCTWebView
を記述する
以下の **5 つの手順** を実行して、Xcode を使用して iOS プロジェクトを準備する必要があります。
- CocoPods によって生成された Xcode ワークスペースを開きます。
cd ios
open Demo.xcworkspace

- アプリを右クリックし、[新規グループ] を選択して、新しいグループに `WebView` という名前を付けます。

- `WebView` グループで、[新規]→[テンプレートからファイルを作成] を選択します。

- `Objective-C ファイル` テンプレートを使用し、`RCTWebView` という名前を付けます。

- `RCTWebView.m` を `RCTWebView.mm` に名前変更して、Objective-C++ ファイルにします。
Podfile
...
Demo
├── AppDelegate.h
├── AppDelegate.mm
...
├── RCTWebView.h
├── RCTWebView.mm
└── main.m
ヘッダーファイルと実装ファイルを作成した後、それらを実装することができます。
これは、コンポーネントインターフェースを宣言する `RCTWebView.h` ファイルのコードです。
#import <React/RCTViewComponentView.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTWebView : RCTViewComponentView
// You would declare native methods you'd want to access from the view here
@end
NS_ASSUME_NONNULL_END
このクラスは、`RCTViewComponentView` クラスを拡張する `RCTWebView` を定義します。これは、すべてのネイティブコンポーネントの基本クラスであり、React Native によって提供されます。
実装ファイル(`RCTWebView.mm`)のコードは次のとおりです。
#import "RCTWebView.h"
#import <react/renderer/components/AppSpecs/ComponentDescriptors.h>
#import <react/renderer/components/AppSpecs/EventEmitters.h>
#import <react/renderer/components/AppSpecs/Props.h>
#import <react/renderer/components/AppSpecs/RCTComponentViewHelpers.h>
#import <WebKit/WebKit.h>
using namespace facebook::react;
@interface RCTWebView () <RCTCustomWebViewViewProtocol, WKNavigationDelegate>
@end
@implementation RCTWebView {
NSURL * _sourceURL;
WKWebView * _webView;
}
-(instancetype)init
{
if(self = [super init]) {
_webView = [WKWebView new];
_webView.navigationDelegate = self;
[self addSubview:_webView];
}
return self;
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &oldViewProps = *std::static_pointer_cast<CustomWebViewProps const>(_props);
const auto &newViewProps = *std::static_pointer_cast<CustomWebViewProps const>(props);
// Handle your props here
if (oldViewProps.sourceURL != newViewProps.sourceURL) {
NSString *urlString = [NSString stringWithCString:newViewProps.sourceURL.c_str() encoding:NSUTF8StringEncoding];
_sourceURL = [NSURL URLWithString:urlString];
if ([self urlIsValid:newViewProps.sourceURL]) {
[_webView loadRequest:[NSURLRequest requestWithURL:_sourceURL]];
}
}
[super updateProps:props oldProps:oldProps];
}
-(void)layoutSubviews
{
[super layoutSubviews];
_webView.frame = self.bounds;
}
#pragma mark - WKNavigationDelegate
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
CustomWebViewEventEmitter::OnScriptLoaded result = CustomWebViewEventEmitter::OnScriptLoaded{CustomWebViewEventEmitter::OnScriptLoadedResult::Success};
self.eventEmitter.onScriptLoaded(result);
}
- (BOOL)urlIsValid:(std::string)propString
{
if (propString.length() > 0 && !_sourceURL) {
CustomWebViewEventEmitter::OnScriptLoaded result = CustomWebViewEventEmitter::OnScriptLoaded{CustomWebViewEventEmitter::OnScriptLoadedResult::Error};
self.eventEmitter.onScriptLoaded(result);
return NO;
}
return YES;
}
// Event emitter convenience method
- (const CustomWebViewEventEmitter &)eventEmitter
{
return static_cast<const CustomWebViewEventEmitter &>(*_eventEmitter);
}
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<CustomWebViewComponentDescriptor>();
}
Class<RCTComponentViewProtocol> WebViewCls(void)
{
return RCTWebView.class;
}
@end
このコードは Objective-C++ で記述されており、さまざまな詳細が含まれています。
- `@interface` は 2 つのプロトコルを実装します。
- Codegen によって生成された `RCTCustomWebViewViewProtocol` です。
- Web ビューのナビゲーションイベントを処理するために WebKit フレームワークによって提供される `WKNavigationDelegate` です。
- `WKWebView` をインスタンス化し、サブビューに追加し、`navigationDelegate` を設定する `init` メソッドです。
- コンポーネントの props が変更されたときに React Native によって呼び出される `updateProps` メソッドです。
- カスタムビューのレイアウト方法を記述する `layoutSubviews` メソッドです。
- `WKWebView` がページの読み込みを完了したときに何を行うかを処理できる `webView:didFinishNavigation:` メソッドです。
- prop として受信した URL が有効かどうかを確認する `urlIsValid:(std::string)propString` メソッドです。
- 厳密に型指定された `eventEmitter` インスタンスを取得するためのユーティリティである `eventEmitter` メソッドです。
- Codegen によって生成された `ComponentDescriptor` を返す `componentDescriptorProvider` です。
- アプリケーションに `RCTWebView` を登録するためのヘルパーメソッドである `WebViewCls` です。
AppDelegate.mm
最後に、コンポーネントをアプリに登録できます。`AppDelegate.mm` を更新して、アプリケーションがカスタム WebView コンポーネントを認識するようにします。
#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTBridge+Private.h>
#import "RCTWebView.h"
@implementation AppDelegate
// ...
- (NSDictionary<NSString *,Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents
{
NSMutableDictionary * dictionary = [super thirdPartyFabricComponents].mutableCopy;
dictionary[@"CustomWebView"] = [RCTWebView class];
return dictionary;
}
@end
このコードは、サードパーティライブラリなどの他のソースからのサードパーティコンポーネントの辞書の変更可能なコピーを取得することにより、`thirdPartyFabricComponents` メソッドをオーバーライドします。
次に、Codegen 仕様ファイルで使用されている名前で辞書にエントリを追加します。このようにして、React が `CustomWebView` という名前のコンポーネントを読み込む必要がある場合、React Native は `RCTWebView` をインスタンス化します。
最後に、新しい辞書を返します。
カスタム iOS コンポーネントを使用してアプリをビルドするときに問題が発生する可能性のある、iOS に関するいくつかの問題を認識しています。
- コンポーネントは、現在アプリケーションでは使用できない `yoga/style/Style.h` ヘッダーにアクセスする必要があります。これを修正するには、アプリのヘッダー検索パスビルド設定に `$(PODS_ROOT)/Headers/Private/Yoga` パスを追加します。
- Codegen は、`RCTThirdPartyFabricComponentsProvider` に生成すべきでない行を生成しています。`RCTThirdPartyFabricComponentsProvider.h` ファイルと `RCTThirdPartyFabricComponentsProvider.mm` ファイルの `WebViewCls` シンボルを含む行を削除します。
これらの問題はすでに修正されており、React Native 0.76.1 でリリースされる予定です。
3. ネイティブコンポーネントを使用する
最後に、新しいコンポーネントをアプリで使用できます。生成された `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` Web サイトを読み込むアプリを作成します。
また、アプリは Web ページが読み込まれたときにアラートを表示します。
5. WebView コンポーネントを使用してアプリを実行する
- Android
- iOS
yarn run android
yarn run ios
Android | iOS |
---|---|
![]() | ![]() |