关于 Expo Modules API 背后的设计考虑因素的概述。
Expo 团队维护着大量的库,随着时间的推移和在不断变化的环境中维护原生模块可能具有挑战性。借助 Expo Modules 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 的动态性相结合,使得捕获某些类型的 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.
¥Make it easy to pass data between runtimes
Expo Modules API 完全了解原生函数所需的参数类型。它可以为你预先验证和转换参数,并且字典可以表示为我们称之为 记录 的原生结构。
¥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 传递给原生函数的参数。当涉及到 NSDictionary
或 ReadableMap
时,这尤其容易出错、耗时且难以维护,其中值的类型在运行时是未知的,并且每个属性都需要由开发者单独验证。
¥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
)、复杂内置类型(例如,URL
、CGPoint
、UIColor
、Data
、java.net.URL
、android.graphics.Color
、kotlin.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 订阅者 是一项强大的功能,可让你加入应用的生命周期,而无需将模块代码分散到 MainActivity
和 AppDelegate
类中,也不需要库的用户也这样做。这对于与 持续的原生生成 的顺利集成特别有用,因为它为库提供了一种以可组合方式挂载到应用生命周期事件的机制 - 而不必担心其他库可能在做什么。
¥Android lifecycle listeners and iOS AppDelegate subscribers are a powerful feature that allows you to hook into the lifecycle of your app, without needing to spread the code out for your module across your MainActivity
and AppDelegate
classes or require that users of your library do the same. This is particularly useful for smooth integration with Continuous Native Generation because it provides libraries with a mechanism to hook into app lifecycle events in a composable way — without having to be concerned about what other libraries might be doing.
¥Support the New Architecture while remaining backwards compatible
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.