[ReactNative翻译]React Native JSI 挑战赛

144 阅读6分钟

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

React Native 的新架构解密

image.png

早在 2018 年 React Amsterdam 大会期间,Parashuram 在他的演讲 中介绍了 React Native 的新架构 Fabric。他还撰写了相关内容,并在今年的大会上更详细地介绍了它。Lorenzo Sciandra也撰写了一篇关于该主题的四部分系列,并且有大量推文和讨论讨论 Fabric 对 React Native 的意义以及我们应该看到的性能提升。

新架构的核心功能之一是避免将数据序列化从 JavaScript 到 Native 的桥接(如果您不知道 React Native 的渲染管道是如何工作的,请阅读上述链接之一)。

当我第一次听说这个新架构时,我立即想到这将会改变游戏规则,于是我开始查看源代码,看看它是如何工作的。当 React 0.59 发布时,我发现主分支中关于这些新概念的很多代码都已经在发布的代码中出现了。我决定找出我们可以如何开始使用它!

注: 真正有趣的部分是 JSI,它是 JS 和本地程序之间的粘合剂。阅读新架构时,你还会发现 TurboModules、CodeGen 和 Fabric 的概念。TurboModules 负责自动发现和暴露模块,Fabric 是新的同步渲染引擎,两者都使用 JSI。CodeGen 是在 JS 和 Native 之间自动生成类型化胶合代码的工具。

在询问了贡献者频道中的一些专家并发送了几条推文之后,我与 Eric Lewis取得了联系,并就如何在不通过桥接的情况下将本地代码公开到Javascript 寻求帮助。埃里克很兴奋,也非常支持我,并主动提供了帮助。

@ericlewis: 你不需要任何黑客就能创建自己的 c++ 模块,并将其插入到 JSI 中。这实际上是免费的!

太棒了!我立即启动了 XCode 并开始破解,过了一会儿,我那早已过时的 C++ 技能给我出了点难题,于是我和 Eric 坐在一起,看看我们是否能一起实现这个目标。

事实上,将本地模块直接公开到 JS 并不难。你只需要在项目中加入几个 C++ 类!

您要公开的对象

我们决定采用最简单的方法--公开一个返回数字的方法的类。这个类很简单,用 C++ 编写,包含在本地项目中。

int Test::runTest() const {
  return 1337;
}

如果您需要高亮显示颜色、头文件和 intellisense,文章末尾附有指向源的链接:)

接下来,我们需要编写胶水,在 JavaScript 和本地语言之间执行函数查找和数值转换,这就是 JSI 的作用所在。

绑定

绑定需要一个安装方法和一个获取可在 JavaScript 中调用的函数的方法。

void TestBinding::install(jsi::Runtime &runtime,
 std::shared_ptr<TestBinding> testBinding) {
 auto testModuleName = “nativeTest”;
 auto object = jsi::Object::createFromHostObject(
   runtime, testBinding);
 runtime.global().setProperty(runtime, testModuleName, 
   std::move(object));
}

这就是我遇到麻烦的地方。为了能够使用 JSI 声明,我们需要嵌入文件 jsi.h,而在新的 React Native 项目中,该文件并不是开箱即用的。埃里克知道如何解决这个问题,在编辑了项目配置并添加了一些额外的头文件路径和定义后,我们的代码终于编译成功了(所需更改请参见版本库)。

接下来我们要做的是在绑定中创建一个函数,将测试函数暴露给 JavaScript。这都是模板代码,看起来像这样:

jsi::Value TestBinding::get(jsi::Runtime &runtime, 
 const jsi::PropNameID &name) {
 
 auto methodName = name.utf8(runtime);
 auto &test = *test_;
 
 if (methodName == “runTest”) {
   return jsi::Function::createFromHostFunction(runtime, name, 0,   
     [&test](jsi::Runtime &runtime, const jsi::Value &thisValue, 
       const jsi::Value *arguments, size_t count) -> jsi::Value { 
       return test.runTest();
     });
 }
 
 return jsi::Value::undefined();
}

当我们在 JavaScript 中访问函数时,会调用 get 方法。这段代码基本上是为我们调用的每个函数构建一个封装器,用于转换 JavaScript 参数和返回值,以便在 JavaScript 和本地代码之间来回传递。

安装绑定

我们的下一项任务是使用正确的参数调用 install 函数,以便 React Native 了解它。(这就是 TurboModules 可以提供帮助的地方--我们的解决方案是一个笨办法)。

通过查看 install 函数的签名,我们可以看到我们需要一个指向 jsi::Runtime 对象的指针。我们开始绞尽脑汁,想办法在我们的 Objective-C 代码中找到这个对象。埃里克知道一些这方面的技巧,我们在代码中安装了一个通知,这样当一个有效的运行时可用时,我们就会得到一个回调:

[[NSNotificationCenter defaultCenter] addObserver:self 
 selector:@selector(handleJavaScriptDidLoadNotification:)
 name:RCTJavaScriptDidLoadNotification
 object:bridge];

在通知回调中,通过导入 React/RCTBridge+Private.h> 文件,我们终于可以访问桥接器的内部结构。我们终于准备好了调用网桥安装函数的解决方案:

- (void)handleJavaScriptDidLoadNotification:(
 __unused NSNotification*)notification {
 // Get the RCTCxxBridge from bridge
 RCTCxxBridge* bridge = notification.userInfo[@”bridge”];
 
 // Get the runtime
 facebook::jsi::Runtime* runtime = 
   (facebook::jsi::Runtime*)bridge.runtime;
 
 // Create the Test object
 auto test = std::make_unique<facebook::react::Test>();
 // Create the Test binding
 std::shared_ptr<facebook::react::TestBinding> testBinding_ = 
  std::make_shared<facebook::react::TestBinding>(std::move(test));
 // Install it!!!
 facebook::react::TestBinding::install(
 (*runtime), testBinding_);
}

JavaScript

终于,我们的代码编译完成并开始运行,可以进入 JavaScript 代码了。

调用本地模块是最简单的部分:

console.warn(global.nativeTest.runTest());

当我们在模拟器中运行这个模块,看到屏幕上 React Native 黄色框中显示的神奇数字时,我们非常兴奋:

注释

如需了解我们为实现此功能所做的所有工作,请参阅随附的 repo。它包含了我们所做的所有工作的提交,应该很容易理解。

重新加载和调试目前无法运行。重载失败的原因是,每次我们收到 javascript 已加载的通知时,我们都在安装模块(可以通过设置标志轻松解决)。至于调试,我们还没有研究出其失效的原因。我们随时欢迎您的意见反馈!

结论

玩转即将推出的 React Native 架构的新部分超级有趣。虽然我们的代码中包含了一些黑客手段来安装我们的模块,但我们已经证明,在 React Native 0.59 中,使用 JSI 编写可直接从 JavaScript 调用的原生模块所需的功能已经发布并投入使用。

我非常期待看到这将给库开发人员带来的所有可能性--我们应该可以开始准备编写同步代码了,这比现在通过桥接器传递数据的代码性能要高得多。

再次感谢Eric他利用周六的时间,在一次有趣的远程结对编程会议上完成了这个解决方案。

Thanks to my colleagues in Fram X for supporting my non-billable hours :)

安卓-2019 年 5 月更新

感谢Tom Duncalf,他向项目提交了一份PR,展示了如何在 Android 中做到这一点!!

版本库

github.com/ericlewis/r…