The second, published version of this article explains how to migrate brightModules from the original project to jsiModule.

What is the JSI

React Native JSI (JavaScript Interface) enables faster and easier communication between JavaScript and Native modules. It is also a core part of the Fabric UI layer and Turbo module in React Native’s new architecture.

What’s different about JSI

JSI removes the bridge between native code and JavaScript code, and also saves a lot of JSON serialization and deserialization when both sides call each other. JSI opens new doors for native and JS interaction. Here are some features of JSI:

  1. JavaScript InterfaceAllows us to register methods with the JavaScript runtime. These methods can be passed in the JS environmentglobalObject gets and is called.
  2. We could have used C++ or iOSOCIn theAndroidJava is used to implement these registration methods.
  3. The original use ofbridgeNative modules can be quickly converted to JSI by adding a layer of C++.
  4. iniOSThe end implementation is very simple because C++ andOCIt can be convenient to realize mixed editing.
  5. inAndroidMedium, we need to passJNIDo some transformation.
  6. These methods can be fully synchronous, which means they are not mandatoryAsync. await.

Use JSI in iOS

Next we will step by step in the iOS project using JSI to achieve native and JS communication.

Create a new React Native project

npx react-native init jsiDemo
Copy the code

The iOS side configuration

Create C++ files in the iOS project directory, example.h, example.cpp.

example.h

#ifndef EXAMPLE_H
#define EXAMPLE_H

namespace facebook {
	namespace jsi {
		class Runtime;}}namespace example {
	void install(facebook::jsi::Runtime &jsiRuntime);
}
#endif /* EXAMPLE_H */
Copy the code

example.m

#include "example.h"
#include <jsi/jsi.h>
using namespace facebook::jsi;
using namespace std;

namespace example {
	void install(Runtime &jsiRuntime) {  
    auto helloWorld = Function::createFromHostFunction(jsiRuntime,
                                                       PropNameID::forAscii(jsiRuntime,
                                                                            "helloWorld"),
                                                       0,
                                                       [](Runtime &runtime,
                                                          const Value &thisValue,
                                                          const Value *arguments,
                                                          size_t count) -> Value {
        string helloworld = "helloworld";
        return Value(runtime, String::createFromUtf8(runtime,helloworld));
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "helloWorld".move(helloWorld)); }}Copy the code

In the above code, we create a method using the createFromHostFunction method and register it with the JS runtime using the setProperty method.

Next, we need to create a moudle and execute the install method within the moudle.

We create OC files, simpleJsi. h, simplejsi.mm

SimpleJsi.h

#import <React/RCTBridgeModule.h>
@interface SimpleJsi : NSObject <RCTBridgeModule>
@property (nonatomic, assign) BOOL setBridgeOnMainQueue;
@end
Copy the code

SimpleJsi.mm

#import "SimpleJsi.h"
#import <React/RCTBridge+Private.h>
#import <React/RCTUtils.h>
#import <jsi/jsi.h>
#import "example.h"
#import <sys/utsname.h>

using namespace facebook::jsi;
using namespace std;

@implementation SimpleJsi

@synthesize bridge = _bridge;
@synthesize methodQueue = _methodQueue;

RCT_EXPORT_MODULE()

+ (BOOL)requiresMainQueueSetup {
    
    return YES;
}

- (void)setBridge:(RCTBridge *)bridge {
    _bridge = bridge;
    _setBridgeOnMainQueue = RCTIsMainQueue(a); [self installLibrary]; } - (void)installLibrary {
    
    RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge;
    
    if(! cxxBridge.runtime) {/** * This is a workaround to install library * as soon as runtime becomes available and is * not recommended. If you see random crashes in iOS * global.xxx not found etc. use this. */
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.001 * NSEC_PER_SEC),
                       dispatch_get_main_queue(), ^ {/** When refreshing the app while debugging, the setBridge method is called too soon. The runtime is not ready yet quite often. We need to install library as soon as  runtime becomes available. */
            [self installLibrary];
            
        });
        return;
    }
    
    example::install(*(facebook::jsi::Runtime *)cxxBridge.runtime);
}

@end
Copy the code

In the setBridge method, we call the Install method of Example to register the method.

RN side configuration

Modify the App. Js

import React from 'react';
import type {Node} from 'react';
import {Text, View, Button} from 'react-native';

const App: () = > Node = () = > {
  const [result, setResult] = React.useState();

  const press = () = > {
    setResult(global.helloWorld());
  };
  return (
    // eslint-disable-next-line react-native/no-inline-styles
    <View style={{backgroundColor: '#FFFFFF', height: '100'}} % >
      <View style={{height: '10% '}} / >
      <Button onPress={press} title="Button" />
      <Text>{' call helloWord :' + result}</Text>
    </View>
  );
};

export default App;
Copy the code

When you click the button, you see that the value of Result is HelloWorld.

The results of

Above we implement the JS call native, but no arguments, next we implement a single argument call.

Js calls native methods with arguments

We add the multiply method registration to the install method in example. CPP and take the input parameter from arguments.

auto multiply = Function::createFromHostFunction(jsiRuntime,
                                                     PropNameID::forAscii(jsiRuntime,
                                                                          "multiply"),
                                                     2,
                                                     [](Runtime &runtime,
                                                        const Value &thisValue,
                                                        const Value *arguments,
                                                        size_t count) -> Value {
        int x = arguments[0].getNumber(a);int y = arguments[1].getNumber(a);return Value(x * y); 
    });
jsiRuntime.global().setProperty(jsiRuntime, "multiply".move(multiply));
Copy the code

Then modify app.js

import React from 'react';
import type {Node} from 'react';
import {Text, View, Button} from 'react-native';

const App: () = > Node = () = > {
  const [result, setResult] = React.useState();

  const press = () = > {
    setResult(global.multiply(2.2));
  };
  return (
    // eslint-disable-next-line react-native/no-inline-styles
    <View style={{backgroundColor: '#FFFFFF', height: '100'}} % >
      <View style={{height: '10% '}} / >
      <Button onPress={press} title="Button" />
      <Text>{'2*2 = ' + result}</Text>
    </View>
  );
};

export default App;
Copy the code

The results of

Native call JS

Jsiruntime.global ().getPropertyAsFunction(jsiRuntime, “jsMethod”).call(jsiRuntime); Method implementation.

First we add a JS method to js. We modify app.js to add jsMethod to it.

import React from 'react';
import type {Node} from 'react';
import {Text, View, Button} from 'react-native';

const App: () = > Node = () = > {
  const [result, setResult] = React.useState();

  global.jsMethod = () = > {
    alert('hello jsMethod');
  };

  const press = () = > {
    setResult(global.multiply(2.2));
  };
  return (
    // eslint-disable-next-line react-native/no-inline-styles
    <View style={{backgroundColor: '#FFFFFF', height: '100'}} % >
      <View style={{height: '10% '}} / >
      <Button onPress={press} title="Button" />
      <Text>{'2*2 = ' + result}</Text>
    </View>
  );
};

export default App;
Copy the code

In the primary side, we assume that when entering the application to trigger call js method, we modify the AppDelegate applicationWillEnterForeground method.

- (void)applicationWillEnterForeground:(UIApplication *)application {
  SimpleJsi *jsi = [self.bridge moduleForName:@"SimpleJsi"];
  [jsi calljs];
}
Copy the code

Get the SimpleJsi object through the moduleForName method, and then through the CallJS method in SimpleJsi.

- (void)calljs {
  RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge;
  Runtime &jsiRuntime = *(facebook::jsi::Runtime *)cxxBridge.runtime;
  jsiRuntime.global().getPropertyAsFunction(jsiRuntime, "jsMethod").call(jsiRuntime);
}
Copy the code

The results of

Native calls JS methods with arguments

A multi-parameter call is similar to a no-parameter call, except that the argument list is added after the call method.

First we define methods on the JS side and modify app.js

import React from 'react';
import type {Node} from 'react';
import {Text, View, Button} from 'react-native';

const App: () = > Node = () = > {
  const [result, setResult] = React.useState();

  global.jsMethod = () = > {
    alert('hello jsMethod');
  };

  global.jsMultiply = (x, y) = > {
    alert('x * y = ' + x * y);
  };

  const press = () = > {
    setResult(global.multiply(2.2));
  };
  return (
    // eslint-disable-next-line react-native/no-inline-styles
    <View style={{backgroundColor: '#FFFFFF', height: '100'}} % >
      <View style={{height: '10% '}} / >
      <Button onPress={press} title="Button" />
      <Text>{'2*2 = ' + result}</Text>
    </View>
  );
};

export default App;
Copy the code

Then we modify the call on the native side

AppDelegate.m

- (void)applicationWillEnterForeground:(UIApplication *)application {
  SimpleJsi *jsi =  [self.bridge moduleForName:@"SimpleJsi"];
//  [jsi calljs];
  [jsi callJsMultiply:4 y:4];
}
Copy the code

SimpleJsi.m

- (void)callJsMultiply:(int)x y:(int) y {
  RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge;
  Runtime &jsiRuntime = *(facebook::jsi::Runtime *)cxxBridge.runtime;
  jsiRuntime.global().getPropertyAsFunction(jsiRuntime, "jsMultiply").call(jsiRuntime, x, y);
}
Copy the code

The results of

Call js function arguments on the native side

Arguments [I].getobject (Runtime).getFunction(Runtime).call(Runtime, value); In a way that triggers.

First we register a new method multiplyWithCallback in example. CPP

auto multiplyWithCallback = Function::createFromHostFunction(jsiRuntime, PropNameID::forAscii(jsiRuntime, "multiplyWithCallback"), 3, [](Runtime &runtime, const Value &thisValue, const Value *arguments, size_t count) -> Value { int x = arguments[0].getNumber(); int y = arguments[1].getNumber(); [2].getobject (Runtime).getFunction(Runtime).call(Runtime, x * y); return Value(); }); jsiRuntime.global().setProperty(jsiRuntime, "multiplyWithCallback", move(multiplyWithCallback));Copy the code

Make a call on the js side to modify app.js

import React from 'react';
import type {Node} from 'react';
import {Text, View, Button} from 'react-native';

const App: () = > Node = () = > {
  const [result, setResult] = React.useState();

  global.jsMethod = () = > {
    alert('hello jsMethod');
  };

  global.jsMultiply = (x, y) = > {
    alert('x * y = ' + x * y);
  };

  const press = () = > {
    // setResult(global.multiply(2, 2));
    global.multiplyWithCallback(4.5, alertResult);
  };

  const alertResult = res= > {
    alert(res);
  };

  return (
    // eslint-disable-next-line react-native/no-inline-styles
    <View style={{backgroundColor: '#FFFFFF', height: '100'}} % >
      <View style={{height: '10% '}} / >
      <Button onPress={press} title="Button" />
      <Text>{'2*2 = ' + result}</Text>
    </View>
  );
};

export default App;
Copy the code

After the button is clicked, multiplyWithCallback is called to pass the alertResult method to the native.

The results of

conclusion

Above is an introduction to JSI. For the code in this article, you can reply to JSI to get the GitHub download address.

The problem

In the case of RN Debug, global.xx cannot find the corresponding method, and I have no clue. If you have a solution, please contact me, thank you very much.

The resources

Blog.notesnook.com/getting-sta… Reactnative.maxieewong.com/ github.com/react-nativ… ospfranco.com/