跳到主要内容

原生组件

如果您想构建新的 React Native 组件,以封装 宿主组件,例如 Android 上独有的 CheckBox 或 iOS 上的 UIButton,则应使用 Fabric 原生组件。

本指南将向您展示如何构建 Fabric 原生组件,通过实现一个 Web 视图组件为例进行说明。步骤如下:

  1. 使用 Flow 或 TypeScript 定义 JavaScript 规范。
  2. 配置依赖管理系统,以便从提供的规范生成代码并进行自动链接。
  3. 实现原生代码。
  4. 在应用程序中使用该组件。

您需要一个使用模板生成的纯净应用程序来使用该组件

bash
npx @react-native-community/cli@latest init Demo --install-pods false

创建 WebView 组件

本指南将向您展示如何创建一个 Web 视图组件。我们将使用 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 规范

您的规范必须在 TypeScriptFlow 中定义(有关更多详细信息,请参阅 Codegen 文档)。Codegen 使用它来生成 C++、Objective-C++ 和 Java 代码,以将您的平台代码连接到 React 运行所在的 JavaScript 运行时。

规范文件必须命名为 <MODULE_NAME>NativeComponent.{ts|js} 才能与 Codegen 一起使用。后缀 NativeComponent 不仅是一个约定,实际上 Codegen 使用它来检测规范文件。

将以下规范用于我们的 WebView 组件

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';

type WebViewScriptLoadedEvent = {
result: 'success' | 'error';
};

export interface NativeProps extends ViewProps {
sourceURL?: string;
onScriptLoaded?: BubblingEventHandler<WebViewScriptLoadedEvent> | null;
}

export default codegenNativeComponent<NativeProps>(
'CustomWebView',
) as HostComponent<NativeProps>;

此规范由三个主要部分组成,不包括导入部分:

  • WebViewScriptLoadedEvent 是一个支持数据类型,用于事件需要从原生传递到 JavaScript 的数据。
  • NativeProps 是我们可以在组件上设置的属性的定义。
  • codegenNativeComponent 语句允许我们为自定义组件生成代码,并为用于匹配原生实现的组件定义名称。

与原生模块一样,您可以在 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 平台的操作。

注意

本指南向您展示如何创建一个仅适用于新架构的原生组件。如果您需要同时支持新架构和旧架构,请参阅我们的 向后兼容性指南

现在是编写一些 Android 平台代码以能够渲染 web 视图的时候了。您需要遵循的步骤是:

  • 运行 Codegen
  • 编写 ReactWebView 的代码
  • 编写 ReactWebViewManager 的代码
  • 编写 ReactWebViewPackage 的代码
  • 在应用程序中注册 ReactWebViewPackage

1. 通过 Gradle 运行 Codegen

运行此命令一次以生成您的首选 IDE 可以使用的样板代码。

Demo/
cd android
./gradlew generateCodegenArtifactsFromSchema

Codegen 将生成您需要实现的 ViewManager 接口和 web 视图的 ViewManager 委托。

2. 编写 ReactWebView

ReactWebView 是一个组件,它封装了 Android 原生视图,当使用我们的自定义组件时,React Native 将渲染该视图。

android/src/main/java/com/webview 文件夹中创建一个 ReactWebView.javaReactWebView.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,因此您可以轻松地重用平台已定义的所有属性。

该类定义了三个 Android 构造函数,但将其具体实现推迟到私有的 configureComponent 函数。此函数负责初始化所有组件的特定属性:在本例中,您正在设置 WebView 的布局,并定义 WebClient,您可以使用它来自定义 WebView 的行为。在此代码中,ReactWebView 在页面加载完成时发出一个事件,通过实现 WebClientonPageFinished 方法。

然后,代码定义了一个辅助函数来实际发出事件。要发出事件,您必须:

  • 获取对 ReactContext 的引用;
  • 检索您正在呈现的视图的 surfaceId
  • 获取对与视图关联的 eventDispatcher 的引用;
  • 使用 WritableMap 对象构建事件的有效负载;
  • 创建您需要发送到 JavaScript 的事件对象;
  • 调用 eventDispatcher.dispatchEvent 以发送事件。

文件的最后一部分包含发送事件所需的数据类型的定义:

  • OnScriptLoadedEventResult,包含 OnScriptLoaded 事件的可能结果。
  • 实际的 OnScriptLoadedEvent,它需要扩展 React Native 的 Event 类。

3. 编写 WebViewManager

WebViewManager 是将 React Native 运行时与原生视图连接起来的类。

当 React 收到来自应用程序的指令以渲染特定组件时,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

它持有一个 CustomWebViewManagerDelegate 的引用,这是 Codegen 生成的另一个元素。

然后,它覆盖了 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> 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;
}
};
}
}

ReactWebViewPackage 扩展了 BaseReactPackage 并实现了正确注册我们的组件所需的所有方法。

  • createViewManagers 方法是创建管理自定义视图的 ViewManager 的工厂方法。
  • getModule 方法根据 React Native 需要渲染的视图返回适当的 ViewManager。
  • getReactModuleInfoProvider 提供了在运行时注册模块时所需的所有信息。

5. 在应用程序中注册 ReactWebViewPackage

最后,您需要在应用程序中注册 ReactWebViewPackage。我们通过修改 MainApplication 文件,将 ReactWebViewPackage 添加到 getPackages 函数返回的包列表中来完成此操作。

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://reactjs.ac.cn/"
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