Expo 模块 API:设计考虑

编辑

Expo 模块 API 背后的设计考虑概述。


Expo 团队维护着一大套库,随着时间推移和不断变化的环境,维护原生模块可能是一个挑战。通过 Expo 模块 API,我们着手构建强大的工具,以简化构建和维护这些库的过程。

利用现代语言特性

在维护超过 50 个原生模块的 Expo SDK 多年后,我们发现许多问题都是由于未处理的空值或不正确的类型造成的。现代语言特性可以帮助开发者避免这些错误;例如,缺乏可选类型与 Objective-C 的动态性结合,使得某些类型的错误在 Swift 中会被编译器捕获,而在 Objective-C 中却难以捕获。

编写 React Native 模块的另一个难点是不同平台之间在写原生模块时的语言和范式的上下文切换。由于这些平台之间的差异,这种情况是无法完全避免的。我们感觉需要有一个通用的 API 和文档,以尽可能简化开发,方便单一开发者在多个平台上维护库。

这也是 Expo 模块生态系统从头开始设计以与现代原生语言(Swift 和 Kotlin)一起使用的原因之一。

简化在运行时之间传递数据的过程

Expo 模块 API 对原生函数期望的参数类型有完全的了解。它可以为你进行预验证和转换参数,并且字典可以表示为我们称之为 Records 的原生结构。

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

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

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

支持表现力丰富的面向对象 API

将你的原生模块状态的真实来源保持在一个地方,而不是在 JavaScript 和原生之间分散,并自行处理相关的记账工作。我们称这个特性为 共享对象。例如,expo-sqlite 数据库实例由共享对象支持。有关共享对象的详细文档即将推出。

提供一个安全且可组合的机制以挂钩应用程序生命周期事件

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

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

React Native 版本 0.68 引入了 新架构,为开发者构建移动应用提供了新功能。它由名为 Turbo 模块 的新原生模块系统和名为 Fabric 的新渲染系统组成。 原生库需要进行调整以利用这些新系统。对于 Fabric,它需要更多的工作,因为它不提供任何兼容层,这意味着以旧方式编写的视图管理器不适用于 Fabric,反之亦然——Fabric 原生组件无法与旧的渲染器一起工作。这基本意味着现有库在一段时间内必须支持两种架构,从而增加了技术负担。

新架构主要是用 C++ 编写的,因此你可能最终需要为你的库编写一些 C++ 代码。作为每天都使用高级 JavaScript 的 React Native 开发者,我们通常不愿意编写 C++,因为这是相对较为复杂的语言。此外,在库中包含 C++ 代码会对构建时间产生负面影响,特别是在 Android 上,并且可能会更难以调试。

在设计 Expo 模块 API 时,我们考虑了这些因素,目的是使其与渲染器无关,以便模块不需要知道应用程序是否在新架构上运行,从而显著降低库开发者的成本。