react-native-nitro-modules is a core library that contains highly efficient statically compiled JS to C++ bindings.
It uses JSI to generate C++ templates that can bridge virtually any JS type to a C++ type with minimal overhead.
Install react-native-nitro-modules as a dependency
in your react-native app:
npm i react-native-nitro-modules
cd ios && pod install
If you are building a nitro module yourself, add react-native-nitro-modules as a peerDependency
into your library's package.json
:
{
...
"peerDependencies": {
...
"react-native-nitro-modules": "*"
},
}
Then install react-native-nitro-modules
as a normal dependency
in your library's example/
app as seen above.
react-native-nitro-modules can either be used with-, or without nitrogen, or mixed (some objects are automatically generated, some manually).
When using Nitrogen, all the bindings are automatically generated. You only need to implement C++, Swift or Kotlin interfaces inside your codebase.
All C++ bindings are bridged to JS using "Hybrid Objects".
A Hybrid Object can have both methods and properties (get and set).
Create a C++ Hybrid Object by inheriting from HybridObject
:
#include <NitroModules/HybridObject.hpp>
using namespace margelo::nitro;
class MyHybridObject: public HybridObject {
public:
explicit MyHybridObject(): HybridObject(TAG) {}
public:
// Property (get)
double getNumber() { return 13; }
// Property (set)
void setNumber(double value) { }
// Method
double add(double left, double right) { return left + right; }
public:
void loadHybridMethods() override {
// Call base method to make sure we properly inherit `toString()` and `equals()`
HybridObject::loadHybridMethods();
// Register all methods that need to be exposed to JS
registerHybrids(this, [](Prototype& prototype) {
prototype.registerHybridGetter("number", &MyHybridObject::getNumber);
prototype.registerHybridSetter("number", &MyHybridObject::setNumber);
prototype.registerHybridMethod("add", &MyHybridObject::add);
});
}
private:
static constexpr auto TAG = "MyHybrid";
};
The MyHybridObject
can then be registered in the HybridObjectRegistry
at app startup:
#include <NitroModules/HybridObjectRegistry.hpp>
// Call this at app startup to register the HybridObjects
void load() {
HybridObjectRegistry::registerHybridObjectConstructor(
"MyHybrid",
[]() -> std::shared_ptr<HybridObject> {
return std::make_shared<MyHybridObject>();
}
);
}
Inside your MyHybridObject
, you can use standard C++ types which will automatically be converted to JS using Nitro's JSIConverter<T>
interface.
The following C++ / JS types are supported out of the box:
JS Type | C++ Type | Swift Type | Kotlin Type |
---|---|---|---|
number |
double / int / float |
Double |
Double |
boolean |
bool |
Bool |
Boolean |
string |
std::string |
String |
String |
bigint |
int64_t / uint64_t |
Int64 |
Long |
T[] |
std::vector<T> |
[T] |
Array<T> / PrimitiveArray |
[A, B, C, ...] |
std::tuple<A, B, C, ...> |
(A, B, C) 🟡 (#38) |
❌ |
A | B | C | ... |
std::variant<A, B, C, ...> |
Variant_A_B_C |
Variant_A_B_C |
Record<string, T> |
std::unordered_map<std::string, T> |
Dictionary<String, T> |
Map<std::string, T> |
T? |
std::optional<T> |
T? |
T? |
(T...) => void |
std::function<void (T...)> |
@escaping (T...) -> Void |
(T...) -> Unit |
(T...) => R |
std::function<std::future<R> (T...)> |
❌ | ❌ |
Promise<T> |
std::shared_ptr<Promise<T>> |
Promise<T> |
Promise<T> |
{ ... } |
std::shared_ptr<AnyMap> |
AnyMapHolder |
AnyMap |
ArrayBuffer |
std::shared_ptr<ArrayBuffer> |
ArrayBufferHolder |
ArrayBuffer |
..any HybridObject |
std::shared_ptr<HybridObject> |
HybridObjectSpec |
HybridObject |
..any interface |
T |
T |
T |
..any enum |
T |
T |
T |
..any union |
T |
T |
T |
Since the JSIConverter<T>
is just a template, you can extend it with any other custom types by overloading the interface.
For example, to add support for an enum, overload JSIConverter<MyEnum>
:
#include <NitroModules/JSIConverter.hpp>
enum class MyEnum {
FIRST = 0,
SECOND = 1
};
namespace margelo::nitro {
template <>
struct JSIConverter<MyEnum> {
static inline MyEnum fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
int intValue = JSIConverter<int>::fromJSI(runtime, arg);
return static_cast<MyEnum>(intValue);
}
static inline jsi::Value toJSI(jsi::Runtime& runtime, MyEnum arg) {
int intValue = static_cast<int>(arg);
return JSIConverter<int>::toJSI(runtime, intValue);
}
};
}
Once the JSIConverter<T>
for MyEnum
is defined, you can use the type MyEnum
in C++ methods, getters and setters of HybridObject
s.
And on the JS side, you can simply treat the returned number
(int) as a MyEnum
:
enum MyEnum {
FIRST = 0,
SECOND = 1
}
const value = myHybridObject.getEnumValue() // <-- typed as `MyEnum` instead of `number`
Make sure to always include the header that defines the JSIConverter<MyEnum>
overload inside the MyHybridObject
file, as this is where the JSIConverter<T>
overloads are accessed from.
Nitrogen can automatically generate such JSIConverter<T>
extensions for enums, TypeScript unions, and even structs/objects - so it is generally recommended to use nitrogen.