概述 Expo 提供的用于开发原生模块的 API 和实用程序。
¥What are Expo Modules
有时,在寻求扩展应用的功能时,安装 Expo 包或第三方库是不够的。在这种情况下,你可以编写自定义原生代码来利用原生平台 API 或使用可用的 Android 或 iOS 依赖。幸运的是,Expo 以 Expo 模块的形式对此提供了一流的支持。
¥Sometimes, installing an Expo package or a third-party library is not enough when looking to extend the capabilities of your app. In that scenario, you can write custom native code to tap into native platform APIs or use available Android or iOS dependencies. Luckily, Expo has first class support for this in the form of Expo Modules.
Expo Modules 允许你以一种自然的方式编写原生代码,并且使用最少的样板文件,并且在两个平台上也保持一致。它提供了一组 API 和实用程序来改进为 Expo 和 React Native 开发原生模块的过程并扩展你的应用功能。
¥Expo Modules allow you to write native code in a way that feels natural with minimal boilerplate that is also consistent on both platforms. It provides a set of APIs and utilities to improve the process of developing native modules for Expo and React Native and expand your app capabilities.
使用 Swift 和 Kotlin 创建原生模块。
钩子 Android 活动和应用生命周期事件。
响应 iOS AppDelegate 事件。
配置并选择使用功能。
¥Design considerations
我们的团队需要维护大量的库,并且随着时间的推移和在不断变化的环境中维护原生模块是相当昂贵的。我们发现现有的原生模块的 React Native API 扩展性不是很好,并且在很多层面上给我们带来了一些额外的开销,从加入新的贡献者(缺乏良好的文档)到编写大量的样板代码。为了让维护更容易,让社区更快迭代,我们决定创建一个模块系统,将成本和技术债务降到最低。
¥Our team needs to maintain a large set of libraries, and maintaining native modules over time and in a constantly changing environment is quite expensive. We found out that the existing React Native API for native modules is not scaling very well and is giving us some additional overhead on many levels, from onboarding new contributors (lack of good documentation) to writing a lot of boilerplate code. To make the maintenance easier and let the community iterate faster, we decided to create a module system that reduces the costs and technical debt to a minimum.
¥Why Swift and Kotlin?
经过几年维护 Expo SDK 中的 50 多个原生模块,我们发现许多问题是由未处理的空值或不正确的类型引起的。现代语言功能可以帮助开发者避免这些错误;例如,可选类型的缺乏与 Objective-C 的动态性相结合,使得捕获某些类型的 bug 变得困难,而这些 bug 本来可以被 Swift 的编译器捕获。
¥After several years of maintaining over 50 native modules in the Expo SDK, we have discovered that many issues were caused by unhandled null values or incorrect types. Modern language features can help developers avoid these bugs; for example, the lack of optional types combined with the dynamism of Objective-C made it tough to catch certain classes of bugs that would have been caught by the compiler in Swift.
编写 React Native 模块的另一个困难是在每个平台上编写原生模块的截然不同的语言和范例之间进行上下文切换。由于这些平台之间的差异,无法完全避免。我们认为需要只有一个通用的 API 和文档来尽可能简化开发,并使单个开发者更容易在多个平台上维护库。
¥Another difficulty of writing React Native modules is context switching between the vastly different languages and paradigms for writing native modules on each platform. Due to the differences between these platforms, it cannot be avoided completely. We feel the need to have just one common API and documentation to simplify the development as much as possible and make it easier for a single developer to maintain a library on multiple platforms.
这就是 Expo Modules 生态系统从头开始设计用于现代母语的原因之一:Swift 和科特林。
¥This is one of the reasons why the Expo Modules ecosystem was designed from the ground up to be used with modern native languages: Swift and Kotlin.
¥Passing data between runtimes
我们遇到的另一个大痛点是从 JavaScript 传递到原生函数的参数的验证。当涉及到 NSDictionary
或 ReadableMap
时,这尤其容易出错、耗时且难以维护,其中值的类型在运行时是未知的,并且每个属性都需要由开发者单独验证。Expo 原生模块 API 的一个重要功能是它完全了解原生函数所需的参数类型。它可以为你预先验证并转换参数!字典可以表示为我们称为 记录 的原生结构。知道参数类型后,还可以将 自动转换参数 转换为某些特定于平台的类型(例如,为了方便起见,可以将 { x: number, y: number }
或 [number, number]
转换为 CoreGraphics 的 CGPoint
)。
¥Another big pain point we have encountered is the validation of arguments passed from JavaScript to native functions.
This is especially error-prone, time-consuming, and difficult to maintain when it comes to NSDictionary
or ReadableMap
, where the type of values is unknown in runtime, and each property needs to be validated separately by the developer.
A valuable feature of the Expo native modules API is its full knowledge of the argument types the native function expects. It can pre-validate and convert the arguments for you! The dictionaries can be represented as native structs that we call Records.
Knowing the argument types, it is also possible to automatically convert arguments to some platform-specific types (for example, { x: number, y: number }
or [number, number]
can be translated to CoreGraphics's CGPoint
for your convenience).
¥React Native New Architecture
React Native 0.68 版本引入了 新架构,它为开发者提供了构建移动应用的新功能。它由名为 涡轮增压模块 的新原生模块系统和名为 Fabric 的新渲染系统组成。本地库需要进行调整才能利用这些新系统。对于 Fabric 来说,它需要更多的工作,因为它不提供任何兼容性层,这意味着以旧方式编写的视图管理器不能与 Fabric 一起使用,反之亦然 — Fabric 原生组件不能与旧方式一起使用 渲染器。这基本上意味着现有的库必须在一段时间内为两种架构提供支持,从而增加了技术债务。
¥React Native version 0.68 introduced the New Architecture, which offers developers new capabilities for building mobile apps. It consists of the new native modules system called Turbo Modules and the new rendering system called Fabric. Native libraries need to be adapted to take advantage of these new systems. For Fabric, it needs even more work as it doesn't provide any compatibility layer, which means that view managers written in the old way don't work with Fabric and the other way around — Fabric native components don't work with the old renderer. It basically implies that existing libraries have to provide support for both architectures for a while, increasing the technical debt.
新的架构主要是用 C++ 编写的,因此你最终可能还会为你的库编写一些 C++ 代码。由于我们所有人(React Native 开发者)每天都使用高级 JavaScript,因此我们不太愿意编写 C++,这是相反的。此外,在库中包含 C++ 代码会对构建时间产生负面影响,尤其是在 Android 上,并且可能更难以调试。
¥The new architecture is mostly written in C++, so you may end up writing some C++ code for your library as well. As we all, React Native developers, use high-level JavaScript on a daily basis, we are rather reluctant to write C++, which is on the opposite side of the spectrum. Moreover, including C++ code in the library has a negative impact on build times, especially on Android, and can be more difficult to debug.
我们在设计 Expo Modules API 时考虑到了这些因素,目标是使其与渲染器无关,这样模块就不需要知道应用是否在新架构上运行,从而显着降低了成本 库开发者。
¥We took these into account when designing the Expo Modules API with the goal in mind to make it renderer-agnostic, so that the module doesn't need to know whether the app is run on the new architecture or not, significantly reducing the cost for library developers.