跳到主要内容

在你的原生组件上调用原生函数

在编写新原生组件的基础指南中,你已经了解了如何创建新组件、如何从 JS 侧向原生侧传递属性,以及如何从原生侧向 JS 发射事件。

自定义组件还可以命令式地调用原生代码中实现的一些函数,以实现更高级的功能,例如编程式地重新加载网页。

在本指南中,你将学习如何实现这一点,使用一个新概念:原生命令(Native Commands)。

本指南基于原生组件指南,假设你已经熟悉它,并且熟悉Codegen

1. 更新你的组件规格说明

第一步是更新组件规格,声明 NativeCommand

更新 WebViewNativeComponent.ts 内容如下:

Demo/specs/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>;

这些更改需要你:

  1. react-native 导入 codegenNativeCommands 函数。它告诉 codegen 需要生成 NativeCommands 相关代码;
  2. 定义一个接口,包含想要在原生侧调用的方法。所有原生命令的第一个参数必须是 React.ElementRef 类型;
  3. 导出 Commands 变量,它是调用 codegenNativeCommands 并传入支持命令列表的结果。
注意

在 TypeScript 中,React.ElementRef 已被弃用。正确的类型其实是 React.ComponentRef。但由于 Codegen 的一个 bug,使用 ComponentRef 会导致应用崩溃。我们已经有修复方案,但需要发布新版本的 React Native 才能生效。

2. 更新 App 代码以使用新命令

现在你可以在 App 中使用这个命令了。

打开 App.tsx 并修改如下:

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.dev/"
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;

这里的相关变更是:

  1. 从规格文件导入 Commands 常量。这个对象允许我们调用原生实现的方法。
  2. 使用 useRef 声明对自定义 WebView 组件的引用,并传递给原生命令。
  3. 实现 refresh 函数。该函数首先检测 WebView 的 ref 是否为空,如果不为空,则调用命令。
  4. 添加一个可点击组件,用户点击按钮时调用命令。

其余更改是普通的 React 改动,添加了 Pressable 控件并美化视图布局。

3. 重新运行 Codegen

规格更新后,代码已准备好使用命令,接下来是实现原生代码前,必须重新运行 codegen,生成原生代码所需的新类型定义。

Codegen 是通过 Gradle 任务 generateCodegenArtifactsFromSchema 执行的:

bash
cd android
./gradlew generateCodegenArtifactsFromSchema

BUILD SUCCESSFUL in 837ms
14 actionable tasks: 3 executed, 11 up-to-date

当你构建 Android 应用时会自动运行此任务。

4. 实现原生代码

现在是实现原生变更的时候了,这将使你的 JS 代码能够直接调用原生视图上的方法。

要让视图响应原生命令,只需要修改 ReactWebViewManager 即可。

若你现在直接构建,构建会失败,因为当前的 ReactWebViewManager 没有实现新的 reload 方法。 为了解决构建错误,我们修改 ReactWebViewManager 来实现它。

ReactWebViewManager.java

//...
@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";
//...

这里可以直接调用 view.reload() 方法,因为我们的 ReactWebView 继承自 Android 的 WebView,其自身有可用的 reload 方法。如果你实现的是没有现成方法的自定义功能,可能还需要在被 React Native ViewManager 管理的 Android 视图类中实现对应方法。

5. 运行你的应用

最后,你可以用平常的命令运行你的应用。应用启动后,点击刷新按钮即可看到页面重新加载。

Android
iOS