Saltar al contenido principal
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 →

Avanzado: Tipos C++ Personalizados

nota

Esta guía asume que estás familiarizado con la guía de Módulos Turbo Nativos en C++ Puro. Continuaremos sobre esa base.

Los Módulos Turbo Nativos en C++ incluyen funcionalidad de bridging para la mayoría de tipos estándar std::. Puedes usar estos tipos en tus módulos sin código adicional.

Si deseas agregar soporte para tipos nuevos y personalizados en tu app o biblioteca, debes proporcionar el archivo de cabecera bridging necesario.

Agregar un Tipo Personalizado: Int64

Los Módulos Turbo Nativos en C++ aún no soportan números int64_t porque JavaScript no admite números mayores a 2^53. Para representar números mayores, podemos usar tipo string en JS y convertirlo automáticamente a int64_t en C++.

1. Crear el archivo de cabecera Bridging

El primer paso para soportar un tipo personalizado es definir la cabecera de bridging que maneja la conversión desde la representación JS a C++, y hacia la representación JS desde C++.

  1. En la carpeta shared, añade un nuevo archivo llamado Int64.h

  2. Agrega el siguiente código a ese archivo:

Int64.h
#pragma once

#include <react/bridging/Bridging.h>

namespace facebook::react {

template <>
struct Bridging<int64_t> {
// Converts from the JS representation to the C++ representation
static int64_t fromJs(jsi::Runtime &rt, const jsi::String &value) {
try {
size_t pos;
auto str = value.utf8(rt);
auto num = std::stoll(str, &pos);
if (pos != str.size()) {
throw std::invalid_argument("Invalid number"); // don't support alphanumeric strings
}
return num;
} catch (const std::logic_error &e) {
throw jsi::JSError(rt, e.what());
}
}

// Converts from the C++ representation to the JS representation
static jsi::String toJs(jsi::Runtime &rt, int64_t value) {
return bridging::toJs(rt, std::to_string(value));
}
};

}

Los componentes clave para tu cabecera de bridging personalizada son:

  • Especialización explícita de la estructura Bridging para tu tipo personalizado. En este caso, la plantilla especifica el tipo int64_t.

  • Una función fromJs para convertir desde la representación JS a C++

  • Una función toJs para convertir desde la representación C++ a JS

nota

En iOS, recuerda añadir el archivo Int64.h al proyecto de Xcode.

2. Modificar la Especificación JS

Ahora podemos modificar la especificación JS para añadir un método que use el nuevo tipo. Como siempre, podemos usar Flow o TypeScript para nuestras especificaciones.

  1. Abre specs/NativeSampleTurbomodule

  2. Modifica la especificación como sigue:

NativeSampleModule.ts
import {TurboModule, TurboModuleRegistry} from 'react-native';

export interface Spec extends TurboModule {
readonly reverseString: (input: string) => string;
+ readonly cubicRoot: (input: string) => number;
}

export default TurboModuleRegistry.getEnforcing<Spec>(
'NativeSampleModule',
);

En estos archivos, definimos la función que debe implementarse en C++.

3. Implementar el código nativo

Ahora necesitamos implementar la función declarada en la especificación JS.

  1. Abre el archivo specs/NativeSampleModule.h y aplica estos cambios:
NativeSampleModule.h
#pragma once

#include <AppSpecsJSI.h>
#include <memory>
#include <string>

+ #include "Int64.h"

namespace facebook::react {

class NativeSampleModule : public NativeSampleModuleCxxSpec<NativeSampleModule> {
public:
NativeSampleModule(std::shared_ptr<CallInvoker> jsInvoker);

std::string reverseString(jsi::Runtime& rt, std::string input);
+ int32_t cubicRoot(jsi::Runtime& rt, int64_t input);
};

} // namespace facebook::react

  1. Abre el archivo specs/NativeSampleModule.cpp e implementa la nueva función:
NativeSampleModule.cpp
#include "NativeSampleModule.h"
+ #include <cmath>

namespace facebook::react {

NativeSampleModule::NativeSampleModule(std::shared_ptr<CallInvoker> jsInvoker)
: NativeSampleModuleCxxSpec(std::move(jsInvoker)) {}

std::string NativeSampleModule::reverseString(jsi::Runtime& rt, std::string input) {
return std::string(input.rbegin(), input.rend());
}

+int32_t NativeSampleModule::cubicRoot(jsi::Runtime& rt, int64_t input) {
+ return std::cbrt(input);
+}

} // namespace facebook::react

La implementación importa la biblioteca C++ <cmath> para operaciones matemáticas, luego implementa la función cubicRoot usando el primitivo cbrt de <cmath>.

4. Probar tu Código en la App

Ahora podemos probar el código en nuestra aplicación.

Primero, actualizamos el archivo App.tsx para usar el nuevo método del TurboModule. Luego, podemos compilar para Android e iOS.

  1. Abre App.tsx y aplica estos cambios:
App.tsx
// ...
+ const [cubicSource, setCubicSource] = React.useState('')
+ const [cubicRoot, setCubicRoot] = React.useState(0)
return (
<SafeAreaView style={styles.container}>
<View>
<Text style={styles.title}>
Welcome to C++ Turbo Native Module Example
</Text>
<Text>Write down here the text you want to revert</Text>
<TextInput
style={styles.textInput}
placeholder="Write your text here"
onChangeText={setValue}
value={value}
/>
<Button title="Reverse" onPress={onPress} />
<Text>Reversed text: {reversedValue}</Text>
+ <Text>For which number do you want to compute the Cubic Root?</Text>
+ <TextInput
+ style={styles.textInput}
+ placeholder="Write your text here"
+ onChangeText={setCubicSource}
+ value={cubicSource}
+ />
+ <Button title="Get Cubic Root" onPress={() => setCubicRoot(SampleTurboModule.cubicRoot(cubicSource))} />
+ <Text>The cubic root is: {cubicRoot}</Text>
</View>
</SafeAreaView>
);
}
//...
  1. Para probar en Android, ejecuta yarn android desde la carpeta raíz de tu proyecto.

  2. Para probar en iOS, ejecuta yarn ios desde la carpeta raíz de tu proyecto.

Agregar un Tipo Estructurado Personalizado: Dirección

Este enfoque se puede generalizar a cualquier tipo de dato. Para tipos estructurados, React Native ofrece funciones auxiliares que facilitan su conversión entre JavaScript y C++.

Supongamos que queremos implementar un tipo personalizado Address con estas propiedades:

ts
interface Address {
street: string;
num: number;
isInUS: boolean;
}

1. Definir el tipo en las especificaciones

Primero definamos el nuevo tipo personalizado en las especificaciones JS para que Codegen genere automáticamente el código de soporte.

  1. Abre el archivo specs/NativeSampleModule y realiza estos cambios:
NativeSampleModule (Add Address type and validateAddress function)
import {TurboModule, TurboModuleRegistry} from 'react-native';

+export type Address = {
+ street: string,
+ num: number,
+ isInUS: boolean,
+};

export interface Spec extends TurboModule {
readonly reverseString: (input: string) => string;
+ readonly validateAddress: (input: Address) => boolean;
}

export default TurboModuleRegistry.getEnforcing<Spec>(
'NativeSampleModule',
);

Este código define el nuevo tipo Address y declara una función validateAddress para el Turbo Native Module. Observa que validateFunction requiere un objeto Address como parámetro.

También es posible tener funciones que retornen tipos personalizados.

2. Definir el código de conversión

A partir del tipo Address definido en las especificaciones, Codegen generará dos tipos auxiliares: NativeSampleModuleAddress y NativeSampleModuleAddressBridging.

El primer tipo define la estructura de Address. El segundo contiene la infraestructura para convertir el tipo personalizado entre JS y C++. Solo necesitamos definir una especialización de Bridging que extienda NativeSampleModuleAddressBridging.

  1. Abre el archivo shared/NativeSampleModule.h

  2. Agrega este código:

NativeSampleModule.h (Bridging the Address type)
#include "Int64.h"
#include <memory>
#include <string>

namespace facebook::react {
+ using Address = NativeSampleModuleAddress<std::string, int32_t, bool>;

+ template <>
+ struct Bridging<Address>
+ : NativeSampleModuleAddressBridging<Address> {};
// ...
}

Este código define un alias Address para el tipo genérico NativeSampleModuleAddress. El orden de los genéricos es importante: el primer argumento del template corresponde al primer tipo de dato de la estructura, el segundo al siguiente, etc.

Luego, el código agrega la especialización de Bridging para Address extendiendo NativeSampleModuleAddressBridging generado por Codegen.

nota

Existe una convención para generar estos tipos:

  • La primera parte del nombre siempre es el tipo del módulo (NativeSampleModule en este ejemplo)
  • La segunda parte siempre es el nombre del tipo JS definido en las especificaciones (Address aquí)

3. Implementar el código nativo

Ahora implementaremos la función validateAddress en C++. Primero declaramos la función en el archivo .h, luego la implementamos en .cpp.

  1. Abre shared/NativeSampleModule.h y agrega la definición de la función
NativeSampleModule.h (validateAddress function prototype)
  std::string reverseString(jsi::Runtime& rt, std::string input);

+ bool validateAddress(jsi::Runtime &rt, jsi::Object input);
};

} // namespace facebook::react
  1. Abre shared/NativeSampleModule.cpp y agrega la implementación
NativeSampleModule.cpp (validateAddress implementation)
bool NativeSampleModule::validateAddress(jsi::Runtime &rt, jsi::Object input) {
std::string street = input.getProperty(rt, "street").asString(rt).utf8(rt);
int32_t number = input.getProperty(rt, "num").asNumber();

return !street.empty() && number > 0;
}

En la implementación, el objeto que representa Address es un jsi::Object. Para extraer sus valores usamos los accesores proporcionados por JSI:

  • getProperty() obtiene una propiedad del objeto por nombre

  • asString() convierte la propiedad a jsi::String

  • utf8() convierte jsi::String a std::string

  • asNumber() convierte la propiedad a double

Tras analizar manualmente el objeto, implementamos la lógica necesaria.

nota

Para profundizar en JSI, mira esta excelente charla de App.JS 2024

4. Probar el código en la aplicación

Para probar el código, modificamos el archivo App.tsx.

  1. Abre el archivo App.tsx. Elimina el contenido de la función App().

  2. Reemplaza el cuerpo de la función App() con el siguiente código:

App.tsx (App function body replacement)
const [street, setStreet] = React.useState('');
const [num, setNum] = React.useState('');
const [isValidAddress, setIsValidAddress] = React.useState<
boolean | null
>(null);

const onPress = () => {
let houseNum = parseInt(num, 10);
if (isNaN(houseNum)) {
houseNum = -1;
}
const address = {
street,
num: houseNum,
isInUS: false,
};
const result = SampleTurboModule.validateAddress(address);
setIsValidAddress(result);
};

return (
<SafeAreaView style={styles.container}>
<View>
<Text style={styles.title}>
Welcome to C Turbo Native Module Example
</Text>
<Text>Address:</Text>
<TextInput
style={styles.textInput}
placeholder="Write your address here"
onChangeText={setStreet}
value={street}
/>
<Text>Number:</Text>
<TextInput
style={styles.textInput}
placeholder="Write your address here"
onChangeText={setNum}
value={num}
/>
<Button title="Validate" onPress={onPress} />
{isValidAddress != null && (
<Text>
Your address is {isValidAddress ? 'valid' : 'not valid'}
</Text>
)}
</View>
</SafeAreaView>
);

¡Felicidades! 🎉

Acabas de hacer el bridge de tus primeros tipos desde JS a C++.