原生组件
如果您想要构建新的 React Native 组件,该组件包装了宿主组件,例如 Android 上独特的 CheckBox 或 iOS 上的 UIButton,您应该使用 Fabric 原生组件。
本指南将通过实现一个 Web 视图组件来向您展示如何构建 Fabric 原生组件。执行此操作的步骤如下:
- 使用 Flow 或 TypeScript 定义 JavaScript 规范。
- 配置依赖管理系统以从提供的规范生成代码并自动链接。
- 实现原生代码。
- 在应用程序中使用该组件。
您将需要一个生成的纯模板应用程序来使用该组件
npx @react-native-community/cli@latest init Demo --install-pods false
创建 WebView 组件
本指南将向您展示如何创建 Web 视图组件。我们将使用 Android 的 WebView
组件和 iOS 的 WKWebView
组件来创建该组件。
让我们从创建用于存放组件代码的文件夹结构开始
mkdir -p Demo/{specs,android/app/src/main/java/com/webview}
这将为您提供以下您将要使用的布局
Demo
├── android/app/src/main/java/com/webview
└── ios
└── spec
android/app/src/main/java/com/webview
文件夹是包含我们的 Android 代码的文件夹。ios
文件夹是包含我们的 iOS 代码的文件夹。spec
文件夹是包含 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 {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>;
// @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';
type WebViewScriptLoadedEvent = $ReadOnly<{|
result: "success" | "error",
|}>;
type NativeProps = $ReadOnly<{|
...ViewProps,
sourceURL?: string;
onScriptLoaded?: BubblingEventHandler<WebViewScriptLoadedEvent>?;
|}>;
export default (codegenNativeComponent<NativeProps>(
'CustomWebView',
): HostComponent<NativeProps>);
此规范由三个主要部分组成,不包括导入
WebViewScriptLoadedEvent
是事件需要从原生传递到 JavaScript 的数据的支持数据类型。NativeProps
是我们可以在组件上设置的 props 的定义。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.nativelocalstorage"
}
},
"dependencies": {
为 Codegen 连接所有内容后,我们需要准备原生代码以挂接到生成的代码。
2. 构建您的原生代码
现在是时候编写原生平台代码了,以便当 React 需要渲染视图时,平台可以创建正确的原生视图并将其渲染在屏幕上。
您应该同时处理 Android 和 iOS 平台。
本指南向您展示了如何创建一个仅适用于新架构的原生组件。如果您需要同时支持新架构和旧架构,请参阅我们的向后兼容性指南。
- Android
- iOS
现在是时候编写一些 Android 平台代码来渲染 Web 视图了。您需要遵循的步骤是
- 运行 Codegen
- 编写
ReactWebView
的代码 - 编写
ReactWebViewManager
的代码 - 编写
ReactWebViewPackage
的代码 - 在应用程序中注册
ReactWebViewPackage
1. 通过 Gradle 运行 Codegen
运行一次以生成您选择的 IDE 可以使用的样板代码。
cd android
./gradlew generateCodegenArtifactsFromSchema
Codegen 将生成您需要实现的 ViewManager
接口和 Web 视图的 ViewManager
代理。
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
。在此代码中,通过实现 WebClient
的 onPageFinished
方法,ReactWebView
在页面加载完成后发出一个事件。
然后,代码定义了一个辅助函数来实际发出事件。要发出事件,您必须:
- 获取对
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
函数,该函数必须返回与规范的 codegenNativeComponent
函数调用中使用的名称相同的名称。
createViewInstance
函数负责实例化一个新的 ReactWebView
。
然后,ViewManager 需要定义所有 React 组件的 props 将如何更新原生视图。在本例中,您需要决定如何处理 React 将在 WebView
上设置的 sourceURL
属性。
最后,如果组件可以发出事件,则需要通过重写冒泡事件的 getExportedCustomBubblingEventTypeConstants
或直接事件的 getExportedCustomDirectEventTypeConstants
来映射事件名称。
4. 编写 ReactWebViewPackage
与原生模块一样,原生组件也需要实现 ReactPackage
类。这是一个可以用来在 React Native 运行时中注册组件的对象。
这是 ReactWebViewPackage
的代码
- Java
- Kotlin
package com.webview;
import com.facebook.react.TurboReactPackage;
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 TurboReactPackage {
@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;
}
};
}
}
package com.webview
import com.facebook.react.TurboReactPackage
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 : TurboReactPackage() {
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
继承自 TurboReactPackage
并实现了正确注册我们的组件所需的所有方法。
createViewManagers
方法是创建管理自定义视图的ViewManager
的工厂方法。getModule
方法根据 React Native 需要渲染的视图返回正确的 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 pods install
重要的是,您将看到 Codegen 的日志输出,我们将在 Xcode 中使用它来构建我们的 WebView 原生组件。
您应该谨慎地将生成的代码提交到您的存储库。生成的代码特定于每个版本的 React Native。使用 npm peerDependencies 来限制与 React Native 版本的兼容性。
3. 编写 RCTWebView
我们需要通过完成以下 **5 个步骤** 来使用 Xcode 准备您的 iOS 项目
- 打开 CocoPods 生成的 Xcode 工作区
cd ios
open Demo.xcworkspace
- 右键单击 app 并选择“新建组”,将新组命名为“WebView”。
- 在“WebView”组中,创建“新建”→“使用模板创建文件”。
- 使用“Objective-C 文件”模板,并将其命名为
RCTWebView
。
- 将
RCTWebView.m
重命名为RCTWebView.mm
,使其成为 Objective-C++ 文件
Podfile
...
Demo
├── AppDelegate.h
├── AppDelegate.mm
...
├── RCTWebView.h
├── RCTWebView.mm
└── main.m
创建头文件和实现文件后,您可以开始实现它们。
这是 RCTWebView.h
文件的代码,它声明了组件接口。
#import <React/RCTViewComponentView.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTWebView : RCTViewComponentView
// You would declare native methods you'd want to access from the view here
@end
NS_ASSUME_NONNULL_END
此类定义了一个继承自 RCTViewComponentView
类的 RCTWebView
。这是所有原生组件的基类,由 React Native 提供。
实现文件 (RCTWebView.mm
) 的代码如下
#import "RCTWebView.h"
#import <react/renderer/components/AppSpecs/ComponentDescriptors.h>
#import <react/renderer/components/AppSpecs/EventEmitters.h>
#import <react/renderer/components/AppSpecs/Props.h>
#import <react/renderer/components/AppSpecs/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>();
}
Class<RCTComponentViewProtocol> WebViewCls(void)
{
return RCTWebView.class;
}
@end
此代码是用 Objective-C++ 编写的,包含各种细节
@interface
实现了两个协议RCTCustomWebViewViewProtocol
,由 Codegen 生成;WKNavigationDelegate
,由 WebKit 框架提供,用于处理 Web 视图导航事件;
init
方法,用于实例化WKWebView
,将其添加到子视图,并设置navigationDelegate
;updateProps
方法,当组件的属性发生更改时,React Native 会调用此方法;layoutSubviews
方法,描述自定义视图的布局方式;webView:didFinishNavigation:
方法,允许您处理WKWebView
完成页面加载后的操作;urlIsValid:(std::string)propString
方法,用于检查作为属性接收的 URL 是否有效;eventEmitter
方法,这是一个用于检索强类型eventEmitter
实例的实用程序方法;componentDescriptorProvider
,返回由 Codegen 生成的ComponentDescriptor
;WebViewCls
,这是一个辅助方法,用于在应用程序中注册RCTWebView
。
AppDelegate.mm
最后,您可以在应用程序中注册该组件。 更新 AppDelegate.mm
以使您的应用程序识别我们的自定义 WebView 组件。
#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTBridge+Private.h>
#import "RCTWebView.h"
@implementation AppDelegate
// ...
- (NSDictionary<NSString *,Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents
{
NSMutableDictionary * dictionary = [super thirdPartyFabricComponents].mutableCopy;
dictionary[@"CustomWebView"] = [RCTWebView class];
return dictionary;
}
@end
此代码通过获取来自其他来源(如第三方库)的第三方组件字典的可变副本,重写了 thirdPartyFabricComponents
方法。
然后,它使用 Codegen 规范文件中使用的名称向字典添加一个条目。 这样,当 React 需要加载名为 CustomWebView
的组件时,React Native 将实例化一个 RCTWebView
。
最后,它返回新的字典。
我们已知 iOS 上存在一些问题,这些问题会在使用自定义 iOS 组件构建应用程序时造成一些麻烦。
- 该组件需要访问应用程序当前不可用的
yoga/style/Style.h
头文件。 要解决此问题,请将$(PODS_ROOT)/Headers/Private/Yoga
路径添加到应用程序的头文件搜索路径构建设置中。 - Codegen 在
RCTThirdPartyFabricComponentsProvider
中生成了一行不应该生成的代码。 请从RCTThirdPartyFabricComponentsProvider.h
和RCTThirdPartyFabricComponentsProvider.mm
文件中删除带有WebViewCls
符号的行。
我们已经修复了这些问题,它们将在 React Native 0.76.1 中发布。
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://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
网站。
当网页加载完毕后,该应用程序还会显示一个警告。
5. 使用 WebView 组件运行您的应用程序
- Android
- iOS
yarn run android
yarn run ios
Android | iOS |
---|---|