[ReactNative翻译]React Native JSI:第一部分--入门

1,254 阅读10分钟

本文由 简悦SimpRead 转码,原文地址 blog.notesnook.com

React Native JSI(Javascript Interface)是帮助Javascri...... 之间进行通信的新层。

React Native JSI(Javascript Interface)是帮助Javascript和Native平台之间更容易、更快速沟通的新层。它是React Native与Fabric UI层和Turbo模块重新架构的核心元素。

JSI有什么不同?

JSI消除了在Native(Java/ObjC)和Javascript代码之间建立 "桥梁 "的需要。它还消除了将所有信息序列化/反序列化为JSON的要求,以便在两个世界之间进行通信。JSI通过将Javascript和本地世界紧密联系在一起,为新的可能性打开了大门。基于我的理解,我将帮助你了解更多关于JSI接口的知识。

  1. 1.Javascript接口,它允许我们在Javascript运行时注册方法。这些方法可以通过Javascript世界中的 "global "对象获得。
  2. 这些方法可以完全用C++编写,也可以是与iOS上的Objective C代码和Android上的Java代码沟通的方式。
  3. 任何目前使用传统 "桥梁 "在Javascript和本地世界之间进行通信的本地模块,都可以通过用C++编写一个简单的层来转换为JSI模块。
  4. 4.在iOS上编写这个层很简单,因为C++可以直接在Objective C中运行,因此所有的iOS框架和代码都可以直接使用。
  5. 5.在安卓系统上,我们必须通过JNI来完成这个任务。
  6. 6.这些方法可以是完全同步的,这意味着使用 "async/await "并不是强制性的。

现在我们要创建一个简单的JSI模块,这将帮助我们更好地理解一切。

设置我们的JSI模块

在你想要创建库的目录下打开终端,并运行以下程序。

npx create-react-native-library react-native-simple-jsi

它将会问你一些问题。

重要的部分是在它问到 "你想使用哪种语言 "时选择 "C++ for iOS and Android"。

这将为我们设置一个使用C++代码的基本模块。但是请注意,这不是一个JSI模块。我们需要在Android和iOS上改变一些部分的代码,使其成为一个JSI模块。

导航到刚刚创建的react-native-simple-jsi文件夹,删除example文件夹,然后在其位置创建一个新的例子。

npx react-native init example。

它也会解决所有其他的依赖性。

在Android上配置

让我们为Android配置我们的库。

安卓的前提条件:

  1. 安装NDK。
  2. 安装CMake。

你可以从Android Studio的SDK管理器中安装这两样东西。

CMakeLists.txt.

cmake_minimum_required(VERSION 3.9.0)

add_library(cpp
            SHARED
            ../cpp/example.cpp
            ./cpp-adapter.cpp
)

include_directories(
            ../../react-native/React
            ../../react-native/React/Base
            ../../react-native/ReactCommon/jsi
            ../cpp
)

set_target_properties(
        cpp PROPERTIES
        CXX_STANDARD 17
        CXX_EXTENSIONS OFF
        POSITION_INDEPENDENT_CODE ON
)

target_link_libraries(
        cpp
        android
)

好的,让我们把它变成可消耗的。我们正在连接我们的jsi模块所需的所有不同的库和文件。我们告诉CMake(C++的编译器)如何编译我们的代码,以及在哪些目录中寻找依赖关系。

cmake_minimum_required。编译我们的库所需的CMake的最小版本。

add_library: 我们要告诉编译器,要添加哪些库。

  1. cpp是我们库的名字。
  2. SHARED库是动态链接的,在运行时加载。
  3. 我们包括不同的文件,这些文件是我们的代码运行所需要的。
  4. example.cpp是我们的c++代码所在的地方。

include_directories。这里我们告诉编译器去搜索包含文件。

记得在这里把cpp改成你想要的库名。

build.gradle: 指定CMake的最低版本。

指定编译c++代码时要使用的CMake的最小版本。

  externalNativeBuild {
    cmake {
      path "./CMakeLists.txt"
      version "3.8.0+"
    }
  }

第三步:安装JSI绑定物

在example文件夹内运行yarn add ../,将我们的库添加到example项目中。

在Android Studio中打开example/android文件夹,等待gradle完成构建你的项目。

如果一切按计划进行,你现在应该在Android的侧边栏中看到这个信息

SimpleJsiModule.java

从侧边栏导航到react-native-simple-jsi/android/java/com.reactnativesimplejsi/SimpleJsiModule.java,用以下代码替换它。

package com.reactnativesimplejsi;

import android.util.Log;

import androidx.annotation.NonNull;

import com.facebook.react.bridge.JavaScriptContextHolder;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.module.annotations.ReactModule;

@ReactModule(name = SimpleJsiModule.NAME)
public class SimpleJsiModule extends ReactContextBaseJavaModule {
  public static final String NAME = "SimpleJsi";

  static {
    try {
      // Used to load the 'native-lib' library on application startup.
      System.loadLibrary("cpp");
    } catch (Exception ignored) {
    }
  }

  public SimpleJsiModule(ReactApplicationContext reactContext) {
    super(reactContext);
  }

  @Override
  @NonNull
  public String getName() {
    return NAME;
  }

  private native void nativeInstall(long jsi);

  public void installLib(JavaScriptContextHolder reactContext) {

    if (reactContext.get() != 0) {
      this.nativeInstall(
        reactContext.get()
      );
    } else {
      Log.e("SimpleJsiModule", "JSI Runtime is not available in debug mode");
    }

  }

}

如你所见,这里没有@ReactMethod等。在这个类中,有两件事正在发生。

  1. 我们正在使用System.loadLibrary加载我们的c++库。
  2. 我们有一个installLib函数,它正在寻找javascript运行时的内存参考。get函数基本上返回一个long值。这个值被传递给JNI,在那里我们将安装我们的绑定。

但是我们有一个错误,nativeInstall函数在JNI中不存在。

只要点击创建JNI函数的nativeInstall,当你把光标移到该方法上时,工具提示就会显示出来。

现在如果你打开cpp-adapter.cpp文件。你会看到添加了一个Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall函数。

SimpleJsiModulePackage.java

这个文件并不存在。你必须创建这个java类。

创建一个新的java类并命名为SimpleJsiModulePackage

用以下代码替换。

package com.reactnativesimplejsi;

import com.facebook.react.bridge.JSIModulePackage;
import com.facebook.react.bridge.JSIModuleSpec;
import com.facebook.react.bridge.JavaScriptContextHolder;
import com.facebook.react.bridge.ReactApplicationContext;
import java.util.Collections;
import java.util.List;

public class SimpleJsiModulePackage implements JSIModulePackage {
  @Override
  public List<JSIModuleSpec> getJSIModules(ReactApplicationContext reactApplicationContext, JavaScriptContextHolder jsContext) {

    reactApplicationContext.getNativeModule(SimpleJsiModule.class).installLib(jsContext);

    return Collections.emptyList();
  }
}

在这个类中,我们覆盖了getJSIModules方法并安装了我们的jsi绑定。

在这一点上,我们的模块已经注册并运行。所以我们从react context获取模块,然后调用installLib函数来安装我们的库。

虽然我们可以在本地模块加载时直接这样做,但这并不安全,因为有可能在本地模块准备好时,运行时还没有加载。这个包给了我们更多的控制权,确保当我们调用`installLib'时,运行时是可用的。

为了调用这个方法并安装库,我们必须修改我们应用程序的MainApplication.java

....

import com.facebook.react.bridge.JSIModulePackage;
import com.reactnativesimplejsi.SimpleJsiModulePackage;

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost =
      new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
          return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
          // Packages that cannot be autolinked yet can be added manually here, for SimpleJsiExample:
          // packages.add(new MyReactNativePackage());
          return packages;
        }

        @Override
        protected JSIModulePackage getJSIModulePackage() {
          return new SimpleJsiModulePackage();
        }

        @Override
        protected String getJSMainModuleName() {
          return "index";
        }
      };
.....
  1. 我们正在导入JSIModulePackage
  2. 我们将SimpleJsiModulePackage注册为JSI模块,这样当JS Runtime加载时,我们的jsi绑定也被安装。在ReactNativeHost的实例中,我们重写getJSIModulePackage方法并返回SimpleJsiModulePackage的新实例。

cpp-adapter.cpp.

这是我们的Java本地接口(JNI)适配器,它允许在java和本地c++代码之间进行双向通信。我们可以从java调用c++代码,从c++调用java代码。

下面是我们的适配器的样子。

#include <jni.h>
#include "example.h"

extern "C"
JNIEXPORT void JNICALL
Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall(JNIEnv *env, jobject thiz, jlong jsi) {
    // TODO: implement nativeInstall()
}

假设example包括我们的install函数,我们现在来添加JSI绑定,我将在后面解释。

#include <jni.h>
#include "example.h"

extern "C"
JNIEXPORT void JNICALL
Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall(JNIEnv *env, jobject thiz, jlong jsi) {

    auto runtime = reinterpret_cast<facebook::jsi::Runtime *>(jsi);

    if (runtime) {
        example::install(*runtime);
    }
}

我们从我们的nativeInstall函数中调用example::install,该函数是由java代码调用的。

Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall(JNIEnv *env, jobject thiz, jlong jsi)

  1. JNIEnv: 一个JNI接口指针
  2. jobject: 调用该函数的java类。
  3. 我们的运行时内存引用的long值。

我们用auto runtime = reinterpret_cast<jsi::Runtime *>(jsi);重新解释运行时类,然后调用install(*runtime);来安装我们的绑定。

在iOS上配置

在iOS上的配置比android容易,包括几个简单的步骤。

在example/ios中运行pod安装,在xcode中打开example.xcworkspace。

SimpleJsi.mm

导航到Pods > Development Pods > react-native-simple-jsi > ios并打开SimpleJsi.mm。

用以下代码替换它。

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

@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) {
       example::install(*(facebook::jsi::Runtime *)cxxBridge.runtime);
    }
}

@end
  1. 我们正在告诉React,我们的模块需要在主队列上进行设置。
  2. 我们得到一个bridge的实例,我们将用它来获取运行时间并安装我们的jsi绑定。在这个实例中,我们要检查bridge.runtime是否存在。如果不存在,我们将等待一段时间,然后再次尝试,直到 "bridge.runtime "变得可用。

SimpleJsi.h

#import <React/RCTBridgeModule.h>;

@interface SimpleJsi : NSObject <RCTBridgeModule>;

@property (nonatomic, assign) BOOL setBridgeOnMainQueue;

@end

我们在这里添加一个属性,setBridgeOnMainQueue,它告诉React将桥设置在主队列上。这导致setBridge在我们的模块中与bridge一起被调用。

所以这就是我们如何为android和iOS配置JSI。现在让我们看看在example.cpp中发生了什么,那里有我们的install功能。

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

好了,让我们来做这个可消耗的东西。

  1. 在顶部,你看到我们已经包含了jsi包含文件。
  2. using namespace facebook::jsi;等有助于我们不重复写 "facebook::jsi"。
  3. install函数需要一个参数,那就是我们的JS运行时间。在这个函数中,我们注册了一个名为 "helloWorld "的方法,当我们从javascript代码中调用它时,将返回一个 "hello world "字符串。
  4. Function::createFromHostFunction是一个创建函数的方法,当被调用时,将调用C++代码。
  5. jsiRuntime.global().setProperty是我们将函数与javascript运行时全局对象绑定的地方。

Function::createFromHostFunction(Runtime, PropNameID, paramCount, function)

  1. Runtime: 代表一个JS运行时,我们的javascript代码正在那里运行。
  2. PropNameID: 一个用于查找我们的函数的标识符。它是一个简单的字符串。
  3. paramCount: 这个函数将有的参数数量。在我们的例子中,它是0
  4. function: 当我们从javascript中调用global.helloWorld()时,将调用的函数。

我们的 "函数 "也有4个参数。

  1. 运行时间"。代表一个JS运行时间,我们的javascript代码正在那里运行。
  2. Value &thisValue: 它是对Value类实例的引用,用于在javascript代码中传递JS值。
  3. Value *arguments: 这个函数的参数来自Javascript。
  4. size_t count: 参数的总数。

在这个函数中,我们正在创建一个简单的字符串hello world

然后我们将返回ValueString::createFromUtf8函数帮助我们将c++字符串(std::string)转换成Javascript字符串(jsi::String)值。

在Javascript中调用我们的函数

现在我们可以在javascript代码中调用我们的函数helloWorld。这应该会在屏幕中央显示helloworld。

export default function App() {
  const [result, setResult] = React.useState<number | undefined>();

  React.useEffect(() => {
    setResult(global.helloWorld())
  }, []);

  return (
    <View style={styles.container}>
      <Text>Result: {result}</Text>
    </View>
  );
}

从这里开始,你可以做的事情有无限可能。

调用有多个参数的函数

example.cpp中添加这个新函数。这是一个简单的函数,做两个数字的乘法运算

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

注意,现在我们将paramCount设置为2,因为我们有两个参数。

在Javascript中,我们可以调用

global.multiply(2, 4); // 8

从C++调用一个JS回调

在这里,我们正在做同样的乘法,但不返回其值。相反,我们正在调用一个JS函数。

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();
      arguments[2].getObject(runtime).getFunction(runtime).call(runtime, x * y);
      return Value();
    });

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

While in javascript, we can call the function like this:

  global.multiplyWithCallback(2,4,(a) => {
    console.log(a); // 8
  })

Value

一个Value可以是undefined, null, boolean, number, symbol, string, 或 object

结论

JSI是React Native的一个游戏改变者,它正在改变React Native的工作方式。今天我们已经学会了如何构建一个简单的JSI模块。在下一篇博客中,我将解释如何通过一些简单的步骤将任何Native模块转换成React Native JSI模块。

你可以在Github上找到该库的完整代码+例子](github.com/ammarahm-ed… )。React Native JSI正被积极用于Notesnook Android和iOS应用程序的加密和存储。你可以从这里获得这些应用程序。


www.deepl.com 翻译