原生组件
如果你想构建全新的 React Native 组件,用来封装一个 Host Component,例如 Android 上独特类型的 CheckBox,或者 iOS 上的 UIButton,你应该使用 Fabric 原生组件。
本指南将通过实现一个 web view 组件来展示如何构建 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,我们是以声明式方式将 spec 导出的 JS 组件名称(CustomWebView)映射到将以原生方式实现该组件的 iOS 类。
2. 构建你的原生代码
现在该编写原生平台代码了,这样当 React 需要渲染一个视图时,平台就能创建正确的原生视图并将其渲染到屏幕上。
你应该同时处理 Android 和 iOS 平台。
本指南展示了如何创建一个仅适用于新架构的原生组件。如果你需要同时支持新架构和旧架构,请参考我们的 向后兼容指南。
- Android
- iOS
现在是时候编写一些 Android 平台代码,以便能够渲染 web view 了。你需要遵循的步骤是:
- 运行 Codegen
- 编写
ReactWebView的代码 - 编写
ReactWebViewManager的代码 - 编写
ReactWebViewPackage的代码 - 在应用中注册
ReactWebViewPackage
1. 通过 Gradle 运行 Codegen
运行一次以生成你所使用的 IDE 可以使用的样板代码。
cd android
./gradlew generateCodegenArtifactsFromSchema
Codegen 将生成你需要实现的 ViewManager 接口,以及 web view 的 ViewManager delegate。
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 接收到来自应用的指令,要渲染某个特定组件时,React 会使用已注册的视图管理器来创建视图并传递所有必需的属性。
下面是 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 的所有组件属性将如何更新原生视图。在这个示例中,你需要决定如何处理 React 会在 WebView 上设置的 sourceURL 属性。
最后,如果组件可以发出事件,你需要通过重写 getExportedCustomBubblingEventTypeConstants 来映射冒泡事件的事件名,或者通过 getExportedCustomDirectEventTypeConstants 来映射直接事件的事件名。
4. 编写 ReactWebViewPackage
和原生模块一样,原生组件也需要实现 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 view 了。你需要遵循的步骤如下:
- 运行 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
我们需要通过完成以下 5 个步骤 来使用 Xcode 准备你的 iOS 项目:
- 打开 CocoaPods 生成的 Xcode Workspace:
cd ios
open Demo.xcworkspace
- 右键点击 app 并选择
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);
// 在这里处理你的 props
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;
}
// 事件发射器便利方法
- (const CustomWebViewEventEmitter &)eventEmitter
{
return static_cast<const CustomWebViewEventEmitter &>(*_eventEmitter);
}
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<CustomWebViewComponentDescriptor>();
}
@end
这段代码使用 Objective-C++ 编写,包含各种细节:
@interface实现了两个协议:RCTCustomWebViewViewProtocol,由 Codegen 生成;WKNavigationDelegate,由 WebKit 框架提供,用于处理 web view 导航事件;
init方法用于实例化WKWebView,将其添加到子视图中,并设置navigationDelegate;updateProps方法在组件的 props 发生变化时由 React Native 调用;layoutSubviews方法描述自定义视图需要如何布局;webView:didFinishNavigation:方法让你在WKWebView完成页面加载时处理相应逻辑;urlIsValid:(std::string)propString方法检查作为 prop 接收的 URL 是否有效;eventEmitter方法是一个用于获取强类型eventEmitter实例的工具方法componentDescriptorProvider返回由 Codegen 生成的ComponentDescriptor;
添加 WebKit 框架
只有在我们创建 Web view 时才需要这一步。iOS 上的 Web 组件需要链接到 Apple 提供的 WebKit 框架。如果你的组件不需要访问 web 特定功能,可以跳过这一步。
Web view 需要访问 Apple 通过 Xcode 和设备自带的某个框架提供的一些功能:WebKit。
你可以在原生代码中通过在 RCTWebView.mm 中添加的 #import <WebKit/WebKit.h> 这一行看到它。
要在你的应用中链接 WebKit 框架,请按照以下步骤操作:
- 在 Xcode 中,点击你的项目
- 选择 app target
- 选择 General 选项卡
- 向下滚动直到找到 "Frameworks, Libraries, and Embedded Contents" 部分,然后点击
+按钮
- 在搜索栏中筛选 WebKit
- 选择 WebKit framework
- 点击 Add。

3. 使用你的原生组件
最后,你可以在应用中使用这个新组件。将生成的 App.tsx 更新为:
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 |
|---|---|
![]() | ![]() |

