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.
Installation
Inside an app
Install react-native-nitro-modules as a dependency
in your react-native app:
npm i react- native -nitro-modules
cd ios && pod install
Inside a nitro module library
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.
Usage
react-native-nitro-modules can either be used with-, or without nitrogen, or mixed (some objects are automatically generated, some manually).
With Nitrogen
When using Nitrogen, all the bindings are automatically generated. You only need to implement C++, Swift or Kotlin interfaces inside your codebase.
Without Nitrogen
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::shared_ptr<Promise<R> > (T...)>
(T...) -> Promise<T>
(T...) -> Promise<T>
Sync<(T...) => R>
std::function<R (T...)>
@escaping (T...) -> R
(T...) -> R
Error
std::exception_ptr
Error
Throwable
Promise<T>
std::shared_ptr<Promise<T> >
Promise<T>
Promise<T>
{ ... }
std::shared_ptr<AnyMap >
AnyMap
AnyMap
ArrayBuffer
std::shared_ptr<ArrayBuffer >
ArrayBuffer
ArrayBuffer
Date
std::chrono::system_clock::time_point
Date
java.time.Instant
..any HybridObject
std::shared_ptr<HybridObject >
HybridObject
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.