跳到主要內容

原生組件

如果您想建置新的 React Native 組件,以包裝 Host Component,例如 Android 上獨特的 CheckBox 類型,或 iOS 上的 UIButton,則應使用 Fabric 原生組件。

本指南將向您展示如何建置 Fabric 原生組件,方法是實作一個網頁檢視組件。執行此操作的步驟如下

  1. 使用 Flow 或 TypeScript 定義 JavaScript 規格。
  2. 設定依賴性管理系統,以從提供的規格產生程式碼並自動連結。
  3. 實作原生程式碼。
  4. 在應用程式中使用組件。

您將需要一個產生的純範本應用程式來使用組件

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

建立 WebView 組件

本指南將向您展示如何建立 Web View 組件。我們將使用 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 是我們可以在組件上設定的 props 的定義。
  • 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 平台程式碼,以便能夠呈現網頁檢視。您需要遵循的步驟是

  • 執行 Codegen
  • 編寫 ReactWebView 的程式碼
  • 編寫 ReactWebViewManager 的程式碼
  • 編寫 ReactWebViewPackage 的程式碼
  • 在應用程式中註冊 ReactWebViewPackage

1. 透過 Gradle 執行 Codegen

執行此操作一次以產生樣板,您的選擇的 IDE 可以使用。

Demo/
cd android
./gradlew generateCodegenArtifactsFromSchema

Codegen 將產生您需要實作的 ViewManager 介面以及網頁檢視的 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 的佈局,並且正在定義用於自訂 WebView 行為的 WebClient。在此程式碼中,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 組件 props 如何更新原生檢視。在範例中,您需要決定如何處理 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> 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;
}
};
}
}

ReactWebViewPackage 擴展了 BaseReactPackage 並實作了正確註冊我們組件所需的所有方法。

  • createViewManagers 方法是建立管理自訂檢視的 ViewManager 的工廠方法。
  • getModule 方法會傳回適當的 ViewManager,具體取決於 React Native 需要呈現的檢視。
  • 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://react.dev.org.tw/"
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