发布时间:2019年4月14日 - 6分钟阅读
早在2018年,Parashuram就在React Amsterdam大会期间的演讲中介绍了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取得了联系,请他帮助我如何在不通过桥的情况下将native代码暴露给Javascript。Eric很兴奋,超级支持我,并站出来帮忙。
@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声明,我们需要在新的React Native项目中加入
jsi.h文件,而这个文件不是开箱即用的。Eric知道如何解决这个问题,在编辑了项目配置和一些额外的头文件路径和定义后,我们的代码终于可以编译了(所需的修改请参见资源库)。
接下来我们要做的就是在我们的绑定中创建一个函数,将我们的测试函数暴露给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代码中找到这个对象。Eric知道一些这方面的技巧,我们在代码中安装了一个通知,这样当一个有效的运行时可用时,我们就会得到一个回调。
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleJavaScriptDidLoadNotification:)
name:RCTJavaScriptDidLoadNotification
object:bridge];
在通知回调中,我们终于可以通过导入<React/RCTBridge+Private.h>文件来访问桥的内部,该文件暴露了一个运行时对象的getter。我们终于准备好了黑客攻击我们的桥的安装函数的解决方案。
- (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.warning(global.nativeTest.runTest());
当我们在模拟器中运行这个模块,看到屏幕上React Native黄色框中显示的神奇数字时,我们相当兴奋。
笔记
关于我们为实现这个功能所做的工作,请参考包含在其中的repo。它包含了我们所做的所有工作的提交,应该很容易理解。
重载和调试目前还不能工作。重载失败是因为每次收到javascript加载的通知时,我们都在安装我们的模块(可以通过设置一个标志轻松解决)。调试我们还没有调查过为什么不能用。我们对PRs持开放态度!
结束语
玩转即将到来的React Native架构的新部分是超级有趣的。虽然我们的代码包含了一些黑客来让我们的模块安装,但我们已经证明了编写本地模块所需的功能已经在React Native 0.59中发布并工作了,这些模块可以直接从JavaScript使用JSI调用。
我真的很期待看到这将给库开发者带来的所有可能性--我们应该能够开始准备编写同步的代码,而且比今天我们通过桥接传递数据的代码性能更高。
再次感谢Eric,他花了周六的时间,在一次有趣的远程对编程会议中,将这个解决方案黑客化。
感谢我在Fram X的同事们支持我的非计费时间:)
Android-更新2019年5月
感谢Tom Duncalf,他向项目提交了一份PR,展示了如何在Android中做这件事!!!!!!!!!。
仓库
通过www.DeepL.com/Translator (免费版)翻译