原生组件
如果你想构建一个 新架构 的 React Native 组件,该组件可以包装一个 Host Component,例如 Android 上的 CheckBox,或者 iOS 上的 UIButton,你应该使用 Fabric 原生组件。
本指南将以实现一个 web 视图组件为例,展示如何构建 Fabric 原生组件。步骤如下:
- 使用 Flow 或 TypeScript 定义一个 JavaScript 规范。
- 配置依赖管理系统的代码生成功能,并自动链接。
- 实现原生代码。
- 在应用中使用该组件。
你需要一个普通的模板生成应用来使用该组件:
npx @react-native-community/cli@latest init Demo --install-pods false
创建一个 WebView 组件
本指南将展示如何创建一个 Web View 组件。我们将使用 Android 的 WebView 组件和 iOS 的 WKWebView 组件来创建该组件。
首先,创建一个文件夹结构来存放组件的代码:
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 规范
你的规范必须使用 TypeScript 或 Flow 定义(更多详情请参阅 Codegen 文档)。这是由 Codegen 生成 C++、Objective-C++ 和 Java 代码,以连接你的平台代码到 React 运行的 JavaScript 运行时。
规范文件必须命名为 <MODULE_NAME>NativeComponent.{ts|js} 才能被 Codegen 识别。NativeComponent 后缀不仅是一个约定,实际上是由 Codegen 用于检测规范文件。
使用以下规范文件来创建 WebView 组件:
- TypeScript
- Flow
import type {
CodegenTypes,
HostComponent,
ViewProps,
} from 'react-native';
import {codegenNativeComponent} from 'react-native';
type WebViewScriptLoadedEvent = {
result: 'success' | 'error';
};
export interface NativeProps extends ViewProps {
sourceURL?: string;
onScriptLoaded?: CodegenTypes.BubblingEventHandler<WebViewScriptLoadedEvent> | null;
}
export default codegenNativeComponent<NativeProps>(
'CustomWebView',
) as HostComponent<NativeProps>;
// @flow strict-local
import type {CodegenTypes, HostComponent, ViewProps} from 'react-native';
import {codegenNativeComponent} from 'react-native';
type WebViewScriptLoadedEvent = $ReadOnly<{|
result: "success" | "error",
|}>;
type NativeProps = $ReadOnly<{|
...ViewProps,
sourceURL?: string;
onScriptLoaded?: CodegenTypes.BubblingEventHandler<WebViewScriptLoadedEvent>?;
|}>;
export default (codegenNativeComponent<NativeProps>(
'CustomWebView',
): HostComponent<NativeProps>);
该规范文件由三部分组成,不包括导入:
WebViewScriptLoadedEvent是一个支持的数据类型,用于将数据从原生代码传递到 JavaScript。NativeProps是定义可以在组件上设置的属性。codegenNativeComponent语句允许我们为自定义组件生成代码,并定义用于匹配原生实现的名称。
与原生模块一样,你可以在 specs/ 目录中拥有多个规范文件。更多信息请参阅 附录。
2. 配置 Codegen 运行
该规范文件用于 React Native 的 Codegen 工具生成平台特定的接口和样板代码。为此,Codegen 需要知道在哪里找到我们的规范文件以及如何处理它。更新你的 package.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
- iOS
现在,是时候编写一些 Android 平台代码,以便能够渲染 web 视图。以下是需要的步骤:
- 运行 Codegen
- 编写
ReactWebView的代码 - 编写
ReactWebViewManager的代码 - 编写
ReactWebViewPackage的代码 - 在应用中注册
ReactWebViewPackage
1. 使用 Gradle 运行 Codegen
运行一次以生成你的 IDE 可以使用的样板代码。
cd android
./gradlew generateCodegenArtifactsFromSchema
Codegen 将生成你需要实现 ViewManager 接口和 ViewManager 委托的 web 视图。
2. 编写 ReactWebView
ReactWebView 是包装 Android 原生视图的组件,React Native 将在使用自定义组件时渲染它。
在 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,因此你可以轻松地重用平台已经定义的所有属性。
该类定义了三个 Android 构造函数,但将它们的实际实现推迟到私有 configureComponent 函数。此函数负责初始化所有组件的特定属性:在这种情况下,你正在设置 WebView 的布局,并定义你用于自定义 WebView 行为的 WebClient。在此代码中,ReactWebView 在页面加载完成后通过实现 WebClient 的 onPageFinished 方法来发出事件。
然后,代码定义了一个实际发出事件的帮助函数。要发出事件,你必须:
- 获取
ReactContext的引用; - 获取当前所呈现视图的
surfaceId; - 获取与该视图关联的
eventDispatcher引用; - 使用
WritableMap对象构建事件的负载; - 创建需要发送给 JavaScript 的事件对象;
- 调用
eventDispatcher.dispatchEvent发送事件。
文件的最后一部分包含了发送事件所需的数据类型定义:
OnScriptLoadedEventResult,表示OnScriptLoaded事件可能的结果。- 实际的
OnScriptLoadedEvent,它需要继承 React Native 的Event类。
3. 编写 WebViewManager
WebViewManager 是将 React Native 运行时与原生视图连接起来的类。
当 React 收到应用发来的渲染某个组件的指令时,它会使用已注册的 view manager 来创建该视图,并传入所有必需的属性。
下面是 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。
它持有 CustomWebViewManagerDelegate 的引用,这也是由 Codegen 生成的另一个对象。
随后它重写了 getName 函数,该函数必须返回与 spec 中 codegenNativeComponent 调用所使用名称相同的值。
createViewInstance 函数负责实例化一个新的 ReactWebView。
接着,ViewManager 需要定义 React 组件的各个 props 如何更新原生视图。在这个示例中,你需要决定如何处理 React 设置到 WebView 上的 sourceURL 属性。
最后,如果组件可以发出事件,你需要通过重写冒泡事件对应的 getExportedCustomBubblingEventTypeConstants,或直接事件对应的 getExportedCustomDirectEventTypeConstants,来映射事件名称。
4. 编写 ReactWebViewPackage
与 Native Modules 类似,Native Components 也需要实现 ReactPackage 类。你可以使用这个对象在 React Native 运行时中注册组件。
下面是 ReactWebViewPackage 的代码:
- Java
- Kotlin
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> getReactModuleInfos() {
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.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
class ReactWebViewPackage : BaseReactPackage() {
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 继承了 BaseReactPackage,并实现了正确注册组件所需的全部方法。
createViewManagers方法是一个工厂方法,用于创建管理自定义视图的ViewManager。getModule方法会根据 React Native 需要渲染的 View 返回相应的 ViewManager。getReactModuleInfoProvider提供了在运行时注册模块时所需的全部信息。
5. 在应用中注册 ReactWebViewPackage
最后,你需要在应用中注册 ReactWebViewPackage。具体做法是修改 MainApplication 文件,把 ReactWebViewPackage 添加到 getPackages 函数返回的包列表中。
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()
}
}
}
现在,是时候编写一些 iOS 平台代码,以便能够渲染 web 视图。以下是需要的步骤:
- 运行 Codegen。
- 编写
RCTWebView的代码 - 在应用中注册
RCTWebView
1. 运行 Codegen
你可以手动运行 Codegen,当然直接使用你将要演示组件的应用来完成此操作会更简单。
cd ios
bundle install
bundle exec pod install
重要的是你会看到 Codegen 的日志输出,我们将在 Xcode 中使用它来构建 WebView 原生组件。
注意不要将生成的代码提交到你的仓库中。生成的代码与 React Native 的版本相关。使用 npm peerDependencies 来约束与 React Native 版本的兼容性。
3. 编写 RCTWebView
我们需要使用 Xcode 完成以下 5 步:
- 打开 CocoPods 生成的 Xcode workspace 文件:
cd ios
open Demo.xcworkspace
- 右键点击应用,选择
New Group,将新组命名为WebView。
- 在
WebView组中,创建New→File from Template。
- 使用
Objective-C File模板,并命名为RCTWebView。
-
重复步骤 4,创建一个名为
RCTWebView.h的头文件。 -
将
RCTWebView.m重命名为RCTWebView.mm,使其成为 Objective-C++ 文件。
Podfile
...
Demo
├── AppDelegate.swift
...
├── RCTWebView.h
└── RCTWebView.mm
创建头文件和实现文件后,可以开始实现它们。
以下是 RCTWebView.h 文件的代码,声明了组件接口。
#import <React/RCTViewComponentView.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTWebView : RCTViewComponentView
// 你可以在视图中声明你想要访问的原生方法
@end
NS_ASSUME_NONNULL_END
这个类定义了一个 RCTWebView,它扩展了 RCTViewComponentView 类。这是所有原生组件的基础类,由 React Native 提供。
以下是实现文件 (RCTWebView.mm) 的代码:
#import "RCTWebView.h"
#import <react/renderer/components/AppSpec/ComponentDescriptors.h>
#import <react/renderer/components/AppSpec/EventEmitters.h>
#import <react/renderer/components/AppSpec/Props.h>
#import <react/renderer/components/AppSpec/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>();
}
@end
这段代码是用 Objective-C++ 编写的,包含以下细节:
@interface实现了两个协议:RCTCustomWebViewViewProtocol, generated by Codegen;WKNavigationDelegate, 由 WebKit 框架提供,用于处理 web 视图导航事件;
init方法实例化WKWebView,将其添加到子视图,并设置navigationDelegate;updateProps方法,由 React Native 在组件的属性变化时调用;layoutSubviews方法,描述了自定义视图需要如何布局;webView:didFinishNavigation:方法,用于处理WKWebView完成加载页面时需要执行的操作;urlIsValid:(std::string)propString方法检查接收到的 URL 属性是否有效;eventEmitter方法,用于检索强类型的eventEmitter实例;componentDescriptorProvider方法,返回由 Codegen 生成的ComponentDescriptor;
添加 WebKit 框架
此步骤仅在我们创建 Web 视图时才需要。iOS 上的 Web 组件需要链接到 Apple 提供的 WebKit 框架。如果你的组件不需要访问 web 特定的功能,可以跳过此步骤。
Web 视图需要访问 Apple 通过 Xcode 和设备附带的框架之一提供的某些功能:WebKit。
你可以在原生代码中看到 #import <WebKit/WebKit.h> 这一行,它被添加到了 RCTWebView.mm 中。
要在你的应用中链接 WebKit 框架,请按照以下步骤操作:
- 在 Xcode 中,点击你的项目
- 选择应用 target
- 选择 General 选项卡
- 向下滚动直到找到 "Frameworks, Libraries, and Embedded Contents" 部分,然后按下
+按钮
- 在搜索栏中,筛选 WebKit
- 选择 WebKit 框架
- 点击 Add。

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.dev/"
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 组件
- Android
- iOS
yarn run android
yarn run ios
| Android | iOS |
|---|---|
![]() | ![]() |

