React Native JSI implements sample code for RN and native communication

React Native JSI implements sample code for RN and native communication

What is JSI

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

What is different about JSI

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

  1. JavaScript Interface allows us to register methods with the JavaScript runtime. These methods can be obtained and called through the global object in the js environment.
  2. We can use C++ or OC in iOS and Java in Android to implement these registration methods.
  3. Native modules that were originally implemented using the bridge method can be quickly converted to JSI by adding a layer of C++.
  4. The implementation on iOS is very simple because C++ and OC can be easily mixed.
  5. In Android, we need to do some conversion through JNI.
  6. These methods can be completely synchronous, which means that async is not mandatory. await.

Using JSI in iOS

Next, we will use JSI in iOS projects step by step to achieve native and JS communication.
Create a new React Native project

npx react-native init jsiDemo

iOS configuration

Create C++ files, example.h and example.cpp, in the iOS project directory.
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 */

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));
    
 }
}

In the above code, we create a method using the createFromHostFunction method and register it to the js runtime through the setProperty method.
Next, we need to create a module and execute the install method in the module.
We create OC files, SimpleJsi.h, SimpleJsi.mm

SimpleJsi.h

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

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();
    [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

In the setBridge method, we called the install method of example to complete the method registration.

RN side configuration

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.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;

After clicking the button, the value of result is found to be helloworld.

result

Above we implemented js calling native, but without parameters, next we implement a single parameter call.

js calls native methods with parameters

We add the registration of the multiply method in the install method of example.cpp and take the input parameters 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();
        int y = arguments[1].getNumber();
        return Value(x * y); 
    });

jsiRuntime.global().setProperty(jsiRuntime, "multiply", move(multiply));

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;

result

Native call JS

Native calling of js is mainly implemented through jsiRuntime.global().getPropertyAsFunction(jsiRuntime, "jsMethod").call(jsiRuntime); method.
First, we add a js method in js. We modify App.js and add the jsMethod method 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;

On the native side, we assume that the js method is triggered when entering the application, and we modify the applicationWillEnterForeground method in AppDelegate.

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

Get the SimpleJsi object through the moduleForName method, and then use 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);
}

result

Native call to JS method with parameters

Multi-parameter calls are similar to zero-parameter calls, except that a parameter list is added after the call method.
First, we define the method 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;

Then we modify the call of the native end
AppDelegate.m

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

SimpleJsi.m

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

result

Calling js function parameters on the native side

To call the function in the js parameter in the native code, you need to trigger it through arguments[i].getObject(runtime).getFunction(runtime).call(runtime, value); ;.

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();
        //Call callback
        arguments[2].getObject(runtime).getFunction(runtime).call(runtime, x * y);
        
        return Value();
        
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "multiplyWithCallback", move(multiplyWithCallback));

Call 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));
    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;

After clicking the button, multiplyWithCallback will be called to pass the alertResult method to the native method.

result

Summarize

The above is the introduction of JSI in this article. You can reply to JSI in the official account to get the GitHub download address of the code in the article.

question

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

References

https://blog.notesnook.com/getting-started-react-native-jsi/
reactnative.maxieewong.com/
https://github.com/react-native-community/discussions-and-proposals/issues/91
ospfranco.com/

This is the end of this article about the sample code for React Native JSI to implement RN and native communication. For more relevant React Native native communication content, please search for previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope you will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • React Native and Android native communication method

<<:  Detailed steps to install Sogou input method on Ubuntu 20.04

>>:  Self-study of MySql built-in functions knowledge points summary

Recommend

Some suggestions on Vue code readability

Table of contents 1. Make good use of components ...

Tutorial on installing and using virtualenv in Deepin

virtualenv is a tool for creating isolated Python...

Let's talk in detail about the difference between unknown and any in TypeScript

Table of contents Preface 1. unknown vs any 2. Th...

Vue implements internationalization of web page language switching

1. Basic steps 1: Install yarn add vue-i18n Creat...

Summary of solutions to common Linux problems

1. Connect Centos7 under VMware and set a fixed I...

MySQL replication advantages and principles explained in detail

Replication is to transfer the DDL and DML operat...

Docker+selenium method to realize automatic health reporting

This article takes the health reporting system of...

Detailed process of upgrading gcc (version 10.2.0) under CentOS7 environment

Table of contents Short Introduction 1. Check the...

Tutorial on building nextcloud personal network disk with Docker

Table of contents 1. Introduction 2. Deployment E...