在原生组件上调用原生函数
¥Invoking native functions on your native component
在 基础指南 编写新的原生组件中,你已经探索了如何创建新组件、如何将属性从 JS 端传递到原生端以及如何从原生端触发事件到 JS。
¥In the base guide to write a new Native Component, you have explored how to create a new component, how to pass properties from the JS side to the native side, and how to emit events from native side to JS.
自定义组件还可以命令式调用原生代码中实现的一些函数,以实现一些更高级的功能,例如以编程方式重新加载网页。
¥Custom components can also call some of the functions implementated in the native code imperatively, to achieve some more advanced functionalities, such as programmatically reload a web page.
在本指南中,你将学习如何通过使用一个新概念来实现这一点:原生命令。
¥In this guide you'll learn how to achieve this, by using a new concept: Native Commands.
本指南基于 原生组件 指南,并假设你熟悉 原生组件 指南和 Codegen 指南。
¥This guide starts from the Native Components guide and assumes that you are familiar with it and that you are familiar with Codegen.
1. 更新组件规范
¥ Update your component specs
第一步是更新组件规范以声明 NativeCommand
。
¥The first step is to update the component spec to declare the NativeCommand
.
- TypeScript
- Flow
按如下方式更新 WebViewNativeComponent.ts
:
¥Update the WebViewNativeComponent.ts
as it follows:
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
:
¥Update the WebViewNativeComponent.js
as it follows:
// @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>);
这些更改需要你:
¥These changes requires you to:
-
从
react-native
文件中导入codegenNativeCommands
函数。这指示 codegen 必须生成NativeCommands
的代码。¥Import the
codegenNativeCommands
function fromreact-native
. This instruct codegen that it has to generate the code forNativeCommands
-
定义一个接口,其中包含我们想要在原生模块中调用的方法。所有原生命令的第一个参数都必须为
React.ElementRef
类型。¥Define an interface that contains the methods we want to invoke in native. All the Native Commands must have a first parameter of type
React.ElementRef
. -
导出调用
codegenNativeCommands
的结果变量Commands
,并传递支持的命令列表。¥Export the
Commands
variable that is the result of the invocation ofcodegenNativeCommands
, passing a list of the supported commands.
在 TypeScript 中,React.ElementRef
已弃用。正确的类型实际上是 React.ComponentRef
。但是,由于 Codegen 中的一个错误,使用 ComponentRef
会导致应用崩溃。我们已经修复了这个问题,但我们需要发布新版本的 React Native 来应用它。
¥In TypeScript, the React.ElementRef
is deprecated. The correct type to use is actually React.ComponentRef
. However, due to a bug in Codegen, using ComponentRef
will crash the app. We have the fix already, but we need to release a new version of React Native to apply it.
2. 更新应用代码以使用新命令
¥ Update the App code to use the new command
现在你可以在应用中使用该命令了。
¥Now you can use the command in the the app.
- TypeScript
- Flow
打开 App.tsx
文件并进行如下修改:
¥Open the App.tsx
file and modify it as it follows:
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.nodejs.cn/"
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
文件并进行如下修改:
¥Open the App.tsx
file and modify it as it follows:
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.nodejs.cn/"
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;
相关更改如下:
¥The relevant changes here are the following:
-
从规范文件中导入
Commands
常量。Command 是一个对象,它允许我们调用原生方法。¥Import the
Commands
const from the spec file. The Command is an object that let us call the methods we have in native. -
使用
useRef
声明对WebView
自定义原生组件的引用。你需要将此引用传递给原生命令。¥Declare a ref to the
WebView
custom native component usinguseRef
. You need to pass this ref to the native command. -
实现
refresh
函数。此函数检查 WebView 的 ref 是否为空,如果不是,则调用命令。¥Implement the
refresh
function. This function checks that the WebView's ref is not null and if not, it calls the command. -
添加一个可按下按钮,以便在用户点击按钮时调用命令。
¥Add a pressable to call the command when the user taps on the button.
其余更改是常规的 React 更改,用于添加 Pressable
并设置视图样式使其更美观。
¥The remaining changes are regular React changes to add a Pressable
and to style the view so it looks nicer.
3. 重新运行 Codegen
¥ Rerun Codegen
现在规范已更新,代码已准备好使用命令,是时候实现原生代码了。但是,在开始编写原生代码之前,你必须重新运行 Codegen,让它生成原生代码所需的新类型。
¥Now that the specs are updated and the code is ready to use the command, it is time to implement the Native code. However, before diving into writing native code, you have to rerun codegen, to let it generate the new types that are needed by the Native code.
- Android
- iOS
Codegen 通过 generateCodegenArtifactsFromSchema
Gradle 任务执行:
¥Codegen is executed through the generateCodegenArtifactsFromSchema
Gradle task:
cd android
./gradlew generateCodegenArtifactsFromSchema
BUILD SUCCESSFUL in 837ms
14 actionable tasks: 3 executed, 11 up-to-date
这将在你构建 Android 应用时自动运行。
¥This is automatically run when you build your Android application.
Codegen 作为脚本阶段的一部分运行,会自动添加到 CocoaPods 生成的项目中。
¥Codegen is run as part of the script phases that's automatically added to the project generated by CocoaPods.
cd ios
bundle install
bundle exec pod install
输出将如下所示:
¥The output will look like this:
...
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. 实现原生代码
¥ Implement the Native Code
现在是时候实现原生代码的更改了,这将使你的 JS 能够直接调用原生视图上的方法。
¥Now it's time to implement the native changes that will enable your JS to directly invoke methods on your native view.
- Android
- iOS
为了让你的视图响应原生命令,你只需修改 ReactWebViewManager。
¥To let your view respond to the Native Command, you only have to modify the ReactWebViewManager.
如果你现在尝试构建,构建将失败,因为当前的 ReactWebViewManager
未实现新的 reload
方法。为了修复构建错误,让我们修改 ReactWebViewManager
来实现它。
¥If you try to build right now, the build will fail, because the current ReactWebViewManager
does not implement the new reload
method.
To fix the build error, let's modify the ReactWebViewManager
to implement it.
- 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"
}
在本例中,直接调用 view.reload()
方法即可,因为我们的 ReactWebView 继承自 Android 的 WebView
,并且它有一个可直接使用的 reload 方法。如果你要实现自定义视图中不可用的自定义函数,则你可能还需要在由 React Native 的 ViewManager
管理的 Android 视图中实现所需的方法。
¥In this case, it's enough to call directly the view.reload()
method because our ReactWebView inherits from the Android's WebView
and it has a reload method directly available. If you are implementing a custom function, that is not available in your custom view, you might also have to implement the required method in the Android's View that is managed by the React Native's ViewManager
.
为了让你的视图响应原生命令,我们需要在 iOS 上实现一些方法。
¥To let your view respond to the Native Command, we need to implement a couple of methods on iOS.
让我们打开 RCTWebView.mm
文件并进行如下修改:
¥Let's open the RCTWebView.mm
file and let's modify it as it follows:
// 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>();
}
为了让你的视图响应原生命令,你需要进行以下更改:
¥To make your view respond to the Native Commands, you need to apply the following changes:
-
添加
handleCommand:args
函数。此函数由组件基础结构调用来处理命令。每个组件的功能实现都类似:你需要调用 Codegen 为你生成的RCT<componentNameInJS>HandleCommand
函数。RCT<componentNameInJS>HandleCommand
会执行一系列验证,验证我们需要调用的命令是否在受支持的命令之列,以及传递的参数是否与预期一致。如果所有检查都通过,RCT<componentNameInJS>HandleCommand
将调用正确的原生方法。¥Add a
handleCommand:args
function. This function is invoked by the components infrastructure to handle the commands. The function implementation is similar for every component: you need to call anRCT<componentNameInJS>HandleCommand
function that is generated by Codegen for you. TheRCT<componentNameInJS>HandleCommand
perform a bunch of validation, verifying that the command that we need to invoke is among the supported ones and that the parameters passed matches the one expected. If all the checks pass, theRCT<componentNameInJS>HandleCommand
will then invoke the proper native method. -
实现
reload
方法。在本例中,reload
方法调用了 WebKit WebView 的reloadFromOrigin
函数。¥Implement the
reload
method. In this example, thereload
method calls thereloadFromOrigin
function of the WebKit's WebView.
5. 运行你的应用
¥ Run your app
最后,你可以使用常用命令运行你的应用。应用运行后,你可以点击刷新按钮查看页面重新加载。
¥Finally, you can run your app with the usual commands. Once the app is running, you can tap on the refresh button to see the page getting reloaded.