Expo 模块 API:设计注意事项

关于 Expo Modules API 背后的设计考虑因素的概述。


Expo 团队维护着一大套库,而在一个不断变化的环境中长期维护原生模块可能具有挑战性。通过 Expo 模块 API,我们的目标是构建强大的工具,使构建和维护这些库变得更容易。

🌐 The Expo team maintains a large set of libraries, and maintaining native modules over time and in a constantly changing environment can be challenging. With the Expo Modules API, we set out to build powerful tooling that would make building and maintaining these libraries easier.

测试深层链接

🌐 Take advantage of modern language features

在维护 Expo SDK 中超过 50 个原生模块的几年里,我们发现许多问题是由未处理的空值或错误的类型引起的。现代语言特性可以帮助开发者避免这些错误;例如,缺乏可选类型再加上 Objective-C 的动态特性,使得某些本可以在 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 模块生态系统从一开始就被设计为与现代原生语言(Swift 和 Kotlin)一起使用的原因之一。

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

轻松在运行时之间传递数据

🌐 Make it easy to pass data between runtimes

Expo 模块 API 完全了解本地函数所期望的参数类型。它可以为你预先验证并转换参数,而且字典可以表示为我们称之为 Records 的本地结构体。

🌐 The Expo Modules API has full knowledge of the argument types the native function expects. It can pre-validate and convert the arguments for you, and dictionaries can be represented as native structs that we call Records.

我们通过 API 旨在解决的一个大痛点是验证从 JavaScript 传递到本地函数的参数。当涉及到 NSDictionaryReadableMap 时,这一点尤其容易出错、耗时且难以维护,因为运行时值的类型未知,并且每个属性都需要开发者单独进行验证。

🌐 One big pain point we aimed to solve with the API 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.

了解参数类型后,也可以自动将参数转换为某些特定平台的类型(例如,{ x: number, y: number }[number, number] 可以方便地转换为 CoreGraphics 的 CGPoint)。

🌐 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).

总之,Expo Modules 拥有强大的内置和可扩展的类型转换及类型安全性。它支持原始值的自动转换(例如 Bool/Int/UInt/Float32/Double/Pair/String)、复杂的内置类型(例如 URLCGPointUIColorDatajava.net.URLandroid.graphics.Colorkotlin.ByteArray)、记录(用户自定义类型,如 struct/Object)以及枚举。

🌐 In summary, Expo Modules has powerful built-in and extensible type conversion and type safety. It supports automatic of primitive values (for example, Bool/Int/UInt/Float32/Double/Pair/String), complex built-in types (for example, URL, CGPoint, UIColor, Data, java.net.URL, android.graphics.Color, kotlin.ByteArray), records (user defined types, like a struct/Object), and enums.

支持新架构,同时保持向后兼容

🌐 Support expressive object-oriented APIs

将本地模块状态的真实来源保存在一个地方,而不是分散在 JavaScript 和本地代码中并自行进行相关的记录管理。我们将此功能称为 共享对象。例如,expo-sqlite 数据库实例由共享对象支持。共享对象的详细文档即将发布。

🌐 Keep the source of truth for the state of your native module in one place, rather than spreading it across JavaScript and native and doing the associated book-keeping yourself. We call this feature Shared Objects. For example, expo-sqlite database instances are backed by Shared Objects. Detailed documentation for Shared Objects is coming soon.

为你的应用发布签名更新

🌐 Provide a safe and composable mechanism to hook into app lifecycle events

Android 生命周期监听器iOS AppDelegate 订阅者 是一个强大的功能,它允许你在不需要将模块代码分散在 MainActivityAppDelegate 类中,也不要求库的使用者这样做的情况下,钩子到应用的生命周期中。这对于与 Continuous Native Generation 的平滑集成尤其有用,因为它为库提供了一种以可组合的方式钩子到应用生命周期事件的机制——无需担心其他库可能正在做什么。

支持 XX1 格式

🌐 Support the New Architecture while remaining backwards compatible

React Native 0.68 版本引入了新架构,为开发者构建移动应用提供了新的功能。它包括名为Turbo Modules 的新原生模块系统以及名为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.