Saltar al contenido principal
Versión: 0.77
Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Componentes Nativos

Si deseas crear nuevos Componentes de React Native que encapsulen un Componente Host como un tipo único de CheckBox en Android o un UIButton en iOS, deberás usar un Componente Nativo de Fabric.

Esta guía te mostrará cómo construir Componentes Nativos de Fabric implementando un componente de vista web. Los pasos para hacerlo son:

  1. Definir una especificación en JavaScript usando Flow o TypeScript.

  2. Configurar el sistema de gestión de dependencias para generar código a partir de la especificación y vincularlo automáticamente.

  3. Implementar el código nativo.

  4. Utilizar el componente en una aplicación.

Necesitarás una aplicación generada con una plantilla básica para usar el componente:

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

Creación de un Componente WebView

Esta guía te mostrará cómo crear un componente WebView. Lo construiremos usando el componente WebView de Android y el componente WKWebView de iOS.

Comencemos creando la estructura de carpetas para alojar el código de nuestro componente:

bash
mkdir -p Demo/{specs,android/app/src/main/java/com/webview}

Esto te dará la siguiente estructura donde trabajarás:

Demo
├── android/app/src/main/java/com/webview
└── ios
└── specs
  • La carpeta android/app/src/main/java/com/webview contendrá nuestro código de Android.

  • La carpeta ios contendrá nuestro código de iOS.

  • La carpeta specs contendrá el archivo de especificación de Codegen.

1. Definir la especificación para Codegen

Tu especificación debe definirse en TypeScript o Flow (consulta la documentación de Codegen para más detalles). Esto lo utiliza Codegen para generar el código C++, Objective-C++ y Java que conecta tu código de plataforma con el entorno de ejecución JavaScript donde funciona React.

El archivo de especificación debe nombrarse <MODULE_NAME>NativeComponent.{ts|js} para funcionar con Codegen. El sufijo NativeComponent no es solo una convención, sino que realmente lo utiliza Codegen para detectar archivos de especificación.

Usa esta especificación para nuestro componente 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>;

Esta especificación se compone de tres partes principales, excluyendo las importaciones:

  • WebViewScriptLoadedEvent es un tipo de dato auxiliar para la información que el evento necesita pasar de nativo a JavaScript.

  • NativeProps es la definición de las props que podemos establecer en el componente.

  • La declaración codegenNativeComponent nos permite generar el código del componente personalizado y define un nombre para el componente que se utilizará para vincularlo con las implementaciones nativas.

Al igual que con los Módulos Nativos, puedes tener múltiples archivos de especificación en el directorio specs/. Para más información sobre los tipos que puedes usar y cómo se mapean a tipos de plataforma, consulta el apéndice.

2. Configurar la ejecución de Codegen

La especificación es utilizada por las herramientas Codegen de React Native para generar interfaces específicas de plataforma y código repetitivo. Para esto, Codegen necesita saber dónde encontrar nuestra especificación y qué hacer con ella. Actualiza tu package.json para incluir:

json
    "start": "react-native start",
"test": "jest"
},
"codegenConfig": {
"name": "AppSpec",
"type": "components",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.webview"
},
"ios": {
"componentProvider": {
"CustomWebView": "RCTWebView"
}
}
},
"dependencies": {

Con todo configurado para Codegen, necesitamos preparar nuestro código nativo para conectarse con el código generado.

Ten en cuenta que para iOS, estamos mapeando declarativamente el nombre del componente JS exportado por la especificación (CustomWebView) con la clase iOS que implementará el componente de forma nativa.

2. Construyendo tu código nativo

Ahora es momento de escribir el código nativo de la plataforma para que, cuando React requiera renderizar una vista, la plataforma pueda crear la vista nativa correcta y mostrarla en pantalla.

Debes trabajar en ambas plataformas: Android e iOS.

nota

Esta guía te muestra cómo crear un componente nativo que solo funciona con la Nueva Arquitectura. Si necesitas soportar tanto la Nueva Arquitectura como la Arquitectura Heredada, consulta nuestra guía de compatibilidad hacia atrás.

Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Ahora es momento de escribir código para la plataforma Android para poder renderizar la vista web. Los pasos que debes seguir son:

  • Ejecutar Codegen

  • Escribir el código para ReactWebView

  • Escribir el código para ReactWebViewManager

  • Escribir el código para ReactWebViewPackage

  • Registrar ReactWebViewPackage en la aplicación

1. Ejecutar Codegen mediante Gradle

Ejecuta esto una vez para generar plantillas que tu IDE pueda utilizar.

Demo/
cd android
./gradlew generateCodegenArtifactsFromSchema

Codegen generará la interfaz ViewManager que debes implementar y el delegado ViewManager para la vista web.

2. Escribir ReactWebView

ReactWebView es el componente que envuelve la vista nativa de Android que React Native renderizará al usar nuestro componente personalizado.

Crea un archivo ReactWebView.java o ReactWebView.kt en la carpeta android/src/main/java/com/webview con este código:

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 extiende el WebView de Android, permitiéndote reutilizar fácilmente todas las propiedades ya definidas por la plataforma.

La clase define los tres constructores de Android pero delega su implementación real a la función privada configureComponent. Esta función se encarga de inicializar todas las propiedades específicas del componente: en este caso configuras el diseño del WebView y defines el WebClient para personalizar el comportamiento del WebView. En este código, ReactWebView emite un evento cuando la página termina de cargar, implementando el método onPageFinished de WebClient.

El código define luego una función auxiliar para emitir eventos. Para emitir un evento, debes:

  • obtener una referencia al ReactContext;

  • recuperar el surfaceId de la vista que estás presentando;

  • obtener una referencia al eventDispatcher asociado con la vista;

  • construir la carga útil del evento usando un objeto WritableMap;

  • crear el objeto de evento que necesitas enviar a JavaScript;

  • llamar a eventDispatcher.dispatchEvent para enviar el evento.

La última parte del archivo contiene la definición de los tipos de datos necesarios para enviar el evento:

  • OnScriptLoadedEventResult, con los posibles resultados del evento OnScriptLoaded.

  • El evento OnScriptLoadedEvent en sí, que debe extender la clase Event de React Native.

3. Escribir WebViewManager

WebViewManager es la clase que conecta el entorno de ejecución de React Native con la vista nativa.

Cuando React recibe la instrucción desde la app para renderizar un componente específico, utiliza el administrador de vistas registrado para crear la vista y pasar todas las propiedades requeridas.

Este es el código de 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 extiende la clase SimpleViewManager de React e implementa CustomWebViewManagerInterface, generado por Codegen.

Contiene una referencia al CustomWebViewManagerDelegate, otro elemento generado por Codegen.

Luego sobrescribe la función getName, que debe devolver el mismo nombre usado en la llamada a codegenNativeComponent de la especificación.

La función createViewInstance es responsable de instanciar un nuevo ReactWebView.

Luego, el ViewManager debe definir cómo las props de los componentes de React actualizarán la vista nativa. En este ejemplo, debes decidir cómo manejar la propiedad sourceURL que React establecerá en el WebView.

Finalmente, si el componente puede emitir eventos, debes mapear el nombre del evento sobrescribiendo getExportedCustomBubblingEventTypeConstants para eventos burbujeantes, o getExportedCustomDirectEventTypeConstants para eventos directos.

4. Escribe el ReactWebViewPackage

Al igual que con los Módulos Nativos, los Componentes Nativos también necesitan implementar la clase ReactPackage. Este objeto se utiliza para registrar el componente en el entorno de ejecución de React Native.

Este es el código para el 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;
}
};
}
}

El ReactWebViewPackage extiende BaseReactPackage e implementa todos los métodos requeridos para registrar correctamente nuestro componente.

  • El método createViewManagers es la fábrica que crea los ViewManager que gestionan las vistas personalizadas.

  • El método getModule devuelve el ViewManager adecuado según la vista que React Native necesite renderizar.

  • El getReactModuleInfoProvider proporciona toda la información requerida al registrar el módulo en el entorno de ejecución.

5. Registra el ReactWebViewPackage en la aplicación

Finalmente, debes registrar el ReactWebViewPackage en la aplicación. Esto se hace modificando el archivo MainApplication, añadiendo el ReactWebViewPackage a la lista de paquetes devueltos por la función 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. Usa tu componente nativo

Finalmente, puedes usar el nuevo componente en tu aplicación. Actualiza tu archivo generado App.tsx con:

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/"
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;

Este código crea una aplicación que utiliza el nuevo componente WebView que creamos para cargar el sitio web react.dev.

La aplicación también muestra una alerta cuando la página web se carga.

4. Ejecuta tu aplicación usando el componente WebView

bash
yarn run android
AndroidiOS