ネイティブコンポーネントでネイティブ関数を呼び出す
新しいネイティブコンポーネントを作成するための基本ガイドでは、新しいコンポーネントを作成する方法、JS側からネイティブ側へプロパティを渡す方法、ネイティブ側からJSへイベントを発行する方法について学びました。
カスタムコンポーネントは、ネイティブコードで実装された関数の一部を命令的に呼び出して、ウェブページをプログラムでリロードするなど、より高度な機能を実現することもできます。
このガイドでは、新しい概念である「ネイティブコマンド」を使用してこれを実現する方法を学びます。
このガイドはネイティブコンポーネントのガイドから始まり、それに精通しており、Codegenにも精通していることを前提としています。
1. コンポーネントの仕様を更新する
最初のステップは、NativeCommand
を宣言するためにコンポーネントの仕様を更新することです。
- TypeScript
- Flow
WebViewNativeComponent.ts
を以下のように更新します
import type {HostComponent, ViewProps} from 'react-native';
import type {BubblingEventHandler} from 'react-native/Libraries/Types/CodegenTypes';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
+import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
type WebViewScriptLoadedEvent = {
result: 'success' | 'error';
};
export interface NativeProps extends ViewProps {
sourceURL?: string;
onScriptLoaded?: BubblingEventHandler<WebViewScriptLoadedEvent> | null;
}
+interface NativeCommands {
+ reload: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
+}
+export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
+ supportedCommands: ['reload'],
+});
export default codegenNativeComponent<NativeProps>(
'CustomWebView',
) as HostComponent<NativeProps>;
WebViewNativeComponent.js
を以下のように更新します
// @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';
+import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
type WebViewScriptLoadedEvent = $ReadOnly<{|
result: "success" | "error",
|}>;
type NativeProps = $ReadOnly<{|
...ViewProps,
sourceURL?: string;
onScriptLoaded?: BubblingEventHandler<WebViewScriptLoadedEvent>?;
|}>;
+interface NativeCommands {
+ reload: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
+}
+export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
+ supportedCommands: ['reload'],
+});
export default (codegenNativeComponent<NativeProps>(
'CustomWebView',
): HostComponent<NativeProps>);
これらの変更により、以下の作業が必要になります。
codegenNativeCommands
関数をreact-native
からインポートします。これにより、codegen にNativeCommands
のコードを生成するよう指示します。- ネイティブで呼び出したいメソッドを含むインターフェースを定義します。すべてのネイティブコマンドは、最初のパラメーターが
React.ElementRef
型である必要があります。 - サポートされているコマンドのリストを渡して、
codegenNativeCommands
の呼び出し結果であるCommands
変数をエクスポートします。
TypeScript では、React.ElementRef
は非推奨です。正しい型は実際には React.ComponentRef
です。ただし、Codegen のバグにより、ComponentRef
を使用するとアプリがクラッシュします。修正はすでに完了していますが、適用するには React Native の新しいバージョンをリリースする必要があります。
2. 新しいコマンドを使用するようにアプリコードを更新する
これで、アプリでコマンドを使用できます。
- TypeScript
- Flow
App.tsx
ファイルを開き、次のように変更します。
import React from 'react';
-import {Alert, StyleSheet, View} from 'react-native';
-import WebView from '../specs/WebViewNativeComponent';
+import {Alert, StyleSheet, Pressable, Text, View} from 'react-native';
+import WebView, {Commands} from '../specs/WebViewNativeComponent';
function App(): React.JSX.Element {
+ const webViewRef = React.useRef<React.ElementRef<typeof View> | null>(null);
+
+ const refresh = () => {
+ if (webViewRef.current) {
+ Commands.reload(webViewRef.current);
+ }
+ };
return (
<View style={styles.container}>
<WebView
+ ref={webViewRef}
sourceURL="https://react.dokyumento.jp/"
style={styles.webview}
onScriptLoaded={() => {
Alert.alert('Page Loaded');
}}
/>
+ <View style={styles.tabbar}>
+ <Pressable onPress={refresh} style={styles.button}>
+ {({pressed}) => (
+ !pressed ? <Text style={styles.buttonText}>Refresh</Text> : <Text style={styles.buttonTextPressed}>Refresh</Text>) }
+ </Pressable>
+ </View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
alignContent: 'center',
},
webview: {
width: '100%',
- height: '100%',
+ height: '90%',
},
+ tabbar: {
+ flex: 1,
+ backgroundColor: 'gray',
+ width: '100%',
+ alignItems: 'center',
+ alignContent: 'center',
+ },
+ button: {
+ margin: 10,
+ },
+ buttonText: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ color: '#00D6FF',
+ width: '100%',
+ },
+ buttonTextPressed: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ color: '#00D6FF77',
+ width: '100%',
+ },
});
export default App;
App.tsx
ファイルを開き、次のように変更します。
import React from 'react';
-import {Alert, StyleSheet, View} from 'react-native';
-import WebView from '../specs/WebViewNativeComponent';
+import {Alert, StyleSheet, Pressable, Text, View} from 'react-native';
+import WebView, {Commands} from '../specs/WebViewNativeComponent';
function App(): React.JSX.Element {
+ const webViewRef = React.useRef<React.ElementRef<typeof View> | null>(null);
+
+ const refresh = () => {
+ if (webViewRef.current) {
+ Commands.reload(webViewRef.current);
+ }
+ };
return (
<View style={styles.container}>
<WebView
+ ref={webViewRef}
sourceURL="https://react.dokyumento.jp/"
style={styles.webview}
onScriptLoaded={() => {
Alert.alert('Page Loaded');
}}
/>
+ <View style={styles.tabbar}>
+ <Pressable onPress={refresh} style={styles.button}>
+ {({pressed}) => (
+ !pressed ? <Text style={styles.buttonText}>Refresh</Text> : <Text style={styles.buttonTextPressed}>Refresh</Text>) }
+ </Pressable>
+ </View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
alignContent: 'center',
},
webview: {
width: '100%',
- height: '100%',
+ height: '90%',
},
+ tabbar: {
+ flex: 1,
+ backgroundColor: 'gray',
+ width: '100%',
+ alignItems: 'center',
+ alignContent: 'center',
+ },
+ button: {
+ margin: 10,
+ },
+ buttonText: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ color: '#00D6FF',
+ width: '100%',
+ },
+ buttonTextPressed: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ color: '#00D6FF77',
+ width: '100%',
+ },
});
export default App;
ここでの関連する変更点は次のとおりです
- Specファイルから
Commands
定数をインポートします。このCommandは、ネイティブで持っているメソッドを呼び出すことができるオブジェクトです。 useRef
を使用して、WebView
カスタムネイティブコンポーネントへの参照を宣言します。この参照をネイティブコマンドに渡す必要があります。refresh
関数を実装します。この関数は、WebViewの参照がnullでないことを確認し、nullでなければコマンドを呼び出します。- ユーザーがボタンをタップしたときにコマンドを呼び出すためのPressableを追加します。
残りの変更は、Pressable
を追加し、見栄えを良くするためにビューをスタイルするための通常のReactの変更です。
3. Codegenを再実行する
仕様が更新され、コードがコマンドを使用する準備が整ったので、ネイティブコードを実装する時です。ただし、ネイティブコードの記述に飛び込む前に、ネイティブコードが必要とする新しい型を生成させるために、codegenを再実行する必要があります。
- Android
- iOS
Codegenは `generateCodegenArtifactsFromSchema` Gradleタスクを通じて実行されます。
cd android
./gradlew generateCodegenArtifactsFromSchema
BUILD SUCCESSFUL in 837ms
14 actionable tasks: 3 executed, 11 up-to-date
これはAndroidアプリケーションをビルドする際に自動的に実行されます。
Codegenは、CocoaPodsによって生成されたプロジェクトに自動的に追加されるスクリプトフェーズの一部として実行されます。
cd ios
bundle install
bundle exec pod install
出力は次のようになります。
...
Framework build type is static library
[Codegen] Adding script_phases to ReactCodegen.
[Codegen] Generating ./build/generated/ios/ReactCodegen.podspec.json
[Codegen] Analyzing /Users/me/src/TurboModuleExample/package.json
[Codegen] Searching for codegen-enabled libraries in the app.
[Codegen] Found TurboModuleExample
[Codegen] Searching for codegen-enabled libraries in the project dependencies.
[Codegen] Found react-native
...
4. ネイティブコードを実装する
これで、JSからネイティブビューのメソッドを直接呼び出せるようにするネイティブの変更を実装する時が来ました。
- Android
- iOS
ビューがネイティブコマンドに応答できるようにするには、ReactWebViewManagerのみを変更する必要があります。
現時点でビルドしようとすると、現在のReactWebViewManager
が新しいreload
メソッドを実装していないため、ビルドが失敗します。ビルドエラーを修正するには、ReactWebViewManager
を修正してそれを実装しましょう。
- Java
- Kotlin
//...
@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<>());
}
+ @Override
+ public void reload(ReactWebView view) {
+ view.reload();
+ }
public static final String REACT_CLASS = "CustomWebView";
//...
@ReactProp(name = "sourceUrl")
override fun setSourceURL(view: ReactWebView, sourceURL: String?) {
if (sourceURL == null) {
view.emitOnScriptLoaded(ReactWebView.OnScriptLoadedEventResult.error)
return;
}
view.loadUrl(sourceURL, emptyMap())
}
+ override fun reload(view: ReactWebView) {
+ view.reload()
+ }
companion object {
const val REACT_CLASS = "CustomWebView"
}
この場合、ReactWebViewはAndroidのWebView
を継承しており、reload()
メソッドが直接利用できるため、view.reload()
メソッドを直接呼び出すだけで十分です。カスタムビューで利用できないカスタム関数を実装する場合は、React NativeのViewManager
によって管理されるAndroidのViewに必要なメソッドを実装する必要があるかもしれません。
ビューがネイティブコマンドに応答できるようにするには、iOSでいくつかのメソッドを実装する必要があります。
RCTWebView.mm
ファイルを開いて、次のように変更します。
// Event emitter convenience method
- (const CustomWebViewEventEmitter &)eventEmitter
{
return static_cast<const CustomWebViewEventEmitter &>(*_eventEmitter);
}
+ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
+ {
+ RCTCustomWebViewHandleCommand(self, commandName, args);
+ }
+
+ - (void)reload
+ {
+ [_webView reloadFromOrigin];
+ }
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<CustomWebViewComponentDescriptor>();
}
ビューがネイティブコマンドに応答するようにするには、以下の変更を適用する必要があります。
handleCommand:args
関数を追加します。この関数は、コンポーネントインフラストラクチャによってコマンドを処理するために呼び出されます。関数の実装はすべてのコンポーネントで似ています。Codegenによって生成されたRCT
関数を呼び出す必要があります。HandleCommand RCT
は、呼び出す必要があるコマンドがサポートされているコマンドの中にあり、渡されたパラメータが期待されるパラメータと一致することを確認するなど、一連の検証を実行します。すべてのチェックに合格した場合、HandleCommand RCT
は適切なネイティブメソッドを呼び出します。HandleCommand reload
メソッドを実装します。この例では、reload
メソッドはWebKitのWebViewのreloadFromOrigin
関数を呼び出します。
5. アプリを実行する
最後に、通常のコマンドでアプリを実行できます。アプリが実行されたら、リフレッシュボタンをタップしてページがリロードされるのを確認できます。
![]() | ![]() |