模块 API 参考

编辑页面

Expo 模块 API 的 API 参考。


For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.

本机模块 API 是建立在 JSI 和 React Native 所依赖的其他底层原语之上的抽象层。它采用现代语言(Swift 和 Kotlin)构建,并提供易于使用且便捷的 API,在可能的情况下在各个平台上保持一致。

定义组件

正如你可能在 Get Started 页面上的代码片段中注意到的,每个模块类都必须实现 definition 函数。 模块定义由描述模块功能和行为的 DSL 组件组成。

名称

设置 JavaScript 代码将用于引用该模块的模块名称。接受一个字符串作为参数。该名称可以从模块的类名推断出来,但为清晰起见,建议显式设置。

Swift / Kotlin
Name("MyModuleName")

常量

定义 JavaScript 对象上的一个常量属性。该属性仅在首次访问时计算一次,之后的访问会返回缓存值。

Swift
Constant("PI") { Double.pi }
Kotlin
Constant("PI") { Math.PI }

常量集合

已弃用: 请改用 Constant

为模块设置常量属性。可以接受一个字典或一个返回字典的闭包。

Swift
// 从字典创建 Constants([ "PI": Double.pi ]) // 或由闭包返回 Constants { return [ "PI": Double.pi ] }
Kotlin
// 作为参数传入 Constants( "PI" to kotlin.math.PI ) // 或由闭包返回 Constants { return@Constants mapOf( "PI" to kotlin.math.PI ) }

函数

定义一个会导出给 JavaScript 的本地同步函数。同步表示当该函数在 JavaScript 中执行时,其本地代码会在同一线程上运行,并阻塞脚本的后续执行,直到本地函数返回。

参数

  • name: String — 你将在 JavaScript 中调用的函数名称。
  • body: (args...) -> ReturnType — 函数被调用时运行的闭包。

该函数最多可接收 8 个参数。这是由于 Swift 和 Kotlin 中泛型的限制,因为该组件必须针对每种参数个数分别实现。

有关函数体中可使用哪些类型的更多详情,请参阅 参数类型 部分。

Swift
Function("mySyncFunction") { (message: String) in return message }
Kotlin
Function("mySyncFunction") { message: String -> return@Function message }
JavaScript
import { requireNativeModule } from 'expo-modules-core'; // 假设我们将模块命名为 "MyModule" const MyModule = requireNativeModule('MyModule'); function getMessage() { return MyModule.mySyncFunction('bar'); }

异步函数

定义一个始终返回 Promise 的 JavaScript 函数,其本地代码默认会在与 JavaScript 运行时不同的线程上派发执行。

参数

  • name: String — 你将在 JavaScript 中调用的函数名称。
  • body: (args...) -> ReturnType — 函数被调用时运行的闭包。

如果最后一个参数的类型是 Promise,则函数会等待该 promise 被 resolve 或 reject 后再将响应传回 JavaScript。否则,该函数会立即以返回值 resolve,或者在抛出异常时 reject。 该函数最多可接收 8 个参数(包括 promise)。

有关函数体中可使用哪些类型的更多详情,请参阅 参数类型 部分。

建议在以下场景中使用 AsyncFunction 而不是 Function

  • 执行 I/O 密集型任务,例如发送网络请求或与文件系统交互
  • 需要在不同线程上运行,例如用于 UI 相关任务的主 UI 线程
  • 属于较大或耗时较长的操作,否则会阻塞 JavaScript 线程,从而降低应用响应速度
Swift
AsyncFunction("myAsyncFunction") { (message: String) in return message } // or AsyncFunction("myAsyncFunction") { (message: String, promise: Promise) in promise.resolve(message) }
Kotlin
AsyncFunction("myAsyncFunction") { message: String -> return@AsyncFunction message } // or // 确保从 `expo.modules.kotlin` 导入 `Promise` 类,而不是 `expo.modules.core`。 AsyncFunction("myAsyncFunction") { message: String, promise: Promise -> promise.resolve(message) }
JavaScript
import { requireNativeModule } from 'expo-modules-core'; // 假设我们将模块命名为 "MyModule" const MyModule = requireNativeModule('MyModule'); async function getMessageAsync() { return await MyModule.myAsyncFunction('bar'); }

可以通过在该组件的返回结果上调用 .runOnQueue 函数来更改 AsyncFunction 的本地队列。

Swift
AsyncFunction("myAsyncFunction") { (message: String) in return message }.runOnQueue(.main)
Kotlin
AsyncFunction("myAsyncFunction") { message: String -> return@AsyncFunction message }.runOnQueue(Queues.MAIN)

Kotlin 协程
Android

AsyncFunction 在 Android 上可以接收可挂起的主体。不过,它必须在 Coroutine 块之后以中缀表示法传入。你可以在 协程概览 中了解更多关于可挂起函数和协程的信息。

带有可挂起主体的 AsyncFunction 不能将 Promise 作为参数接收。它使用挂起机制来执行异步调用。 该函数会立即以所提供的可挂起块的返回值 resolve,或者在抛出异常时 reject。该函数最多可接收 8 个参数。

默认情况下,挂起函数会在模块的协程作用域中派发。此外,从主体块中调用的其他任何可挂起函数也会在同一作用域内运行。 该作用域的生命周期绑定到模块的生命周期——所有未完成的挂起函数都会在模块被释放时被取消。

Kotlin
AsyncFunction("suspendFunction") Coroutine { message: String -> // 你可以在这里执行其他可挂起函数。 // 例如,你可以使用 `kotlinx.coroutines.delay` 来延迟底层 promise 的解析。 delay(5000) return@Coroutine message }

属性

直接在表示本机模块的 JavaScript 对象上定义一个新属性。它与在模块对象上调用 Object.defineProperty 相同。

要声明一个只读属性,你可以使用一种简写语法,需要两个参数:

  • name: String — 你将在 JavaScript 中使用的属性名称。
  • getter: () -> PropertyType — 属性 getter 被调用时运行的闭包。
Swift
Property("foo") { return "bar" }
Kotlin
Property("foo") { return@Property "bar" }

对于可变属性,需要同时提供 getter 和 setter 闭包(使用下面的语法也可以声明只有 setter 的属性):

  • name: String — 你将在 JavaScript 中使用的属性名称。
  • getter: () -> PropertyType — 属性 getter 被调用时运行的闭包。
  • setter: (newValue: PropertyType) -> void — 属性 setter 被调用时运行的闭包。
Swift
Property("foo") .get { return "bar" } .set { (newValue: String) in // 使用新值做一些事情 }
Kotlin
Property("foo") .get { return@get "bar" } .set { newValue: String -> // 使用新值做一些事情 }
JavaScript
import { requireNativeModule } from 'expo-modules-core'; // 假设我们将模块命名为 "MyModule" const MyModule = requireNativeModule('MyModule'); // 获取属性值 MyModule.foo; // 设置新值 MyModule.foo = 'foobar';

视图

允许将模块用作本机视图。作为视图定义一部分接受的定义组件有:PropEventsGroupViewAsyncFunction

视图定义中的 AsyncFunction 会添加到表示本机视图的 React 组件的 React ref 上。 这样的异步函数会自动将本机视图的实例作为第一个参数接收,并默认在 UI 线程上运行。

参数

  • viewType — 将被渲染的本机视图类。注意:在 Android 上,提供的类必须继承自 ExpoView;在 iOS 上则是可选的。参见 Extending ExpoView
  • definition: () -> ViewDefinition — 视图定义的构建器。
Swift
View(UITextView.self) { Prop("text") { %%placeholder-start%%... %%placeholder-end%% } AsyncFunction("focus") { (view: UITextView) in view.becomeFirstResponder() } }
Kotlin
View(TextView::class) { Prop("text") { %%placeholder-start%%... %%placeholder-end%% } AsyncFunction("focus") { view: TextView -> view.requestFocus() } }
支持渲染 SwiftUI 视图的功能正在计划中。现在,你可以使用 UIHostingController 并将其内容视图添加到你的 UIKit 视图中。

事件监听

事件

定义模块可以发送给 JavaScript 的事件名称。

Note: 该组件可在 View 块内使用,以定义回调名称。参见 View callbacks

Swift
Events("onCameraReady", "onPictureSaved", "onBarCodeScanned")
Kotlin
Events("onCameraReady", "onPictureSaved", "onBarCodeScanned")

请参阅 发送事件 了解如何从本地代码向 JavaScript/TypeScript 发送事件。

OnStartObserving

定义在添加第一个事件监听器时调用的函数。

你需要传入一个事件名称,以将监听器限定到特定事件。这在你需要按事件而不是全局地设置或清理资源时很有用。

Swift
// 在添加 "onURLReceived" 的监听器时调用 OnStartObserving("onURLReceived") { %%placeholder-start%%... %%placeholder-end%% }
Kotlin
// 在添加 "onURLReceived" 的监听器时调用 OnStartObserving("onURLReceived") { %%placeholder-start%%... %%placeholder-end%% }

OnStopObserving

定义在移除某个给定事件的所有事件监听器时调用的函数。

OnStartObserving 类似,你需要传入一个事件名称,以将监听器限定到特定事件。

Swift
// 在移除 "onURLReceived" 的监听器时调用 OnStopObserving("onURLReceived") { %%placeholder-start%%... %%placeholder-end%% }
Kotlin
// 在移除 "onURLReceived" 的监听器时调用 OnStopObserving("onURLReceived") { %%placeholder-start%%... %%placeholder-end%% }

生命周期监听器

OnCreate

定义在模块初始化后立即调用的模块生命周期监听器。如果你需要在模块初始化时设置某些内容,请使用它,而不是模块的类初始化器。

OnDestroy

定义在模块即将被释放时调用的模块生命周期监听器。请使用它,而不是模块的类析构函数。

OnAppContextDestroys

定义在拥有该模块的应用上下文即将被释放时调用的模块生命周期监听器。

OnAppEntersForeground

Only for:
iOS

定义在应用即将进入前台模式时调用的监听器。

Note: 该函数在 Android 上不可用——你可能需要改用 OnActivityEntersForeground

OnAppEntersBackground

Only for:
iOS

定义在应用进入后台模式时调用的监听器。

Note: 该函数在 Android 上不可用——你可能需要改用 OnActivityEntersBackground

OnAppBecomesActive

Only for:
iOS

定义在应用再次变为活动状态时调用的监听器(在 OnAppEntersForeground 之后)。

Note: 该函数在 Android 上不可用——你可能需要改用 OnActivityEntersForeground

OnActivityEntersForeground

Only for:
Android

定义在 activity 恢复后立即调用的 activity 生命周期监听器。

Note: 该函数在 iOS 上不可用——你可能需要改用 OnAppEntersForeground

OnActivityEntersBackground

Only for:
Android

定义在 activity 暂停后立即调用的 activity 生命周期监听器。

Note: 该函数在 iOS 上不可用——你可能需要改用 OnAppEntersBackground

OnActivityDestroys

Only for:
Android

定义在拥有 JavaScript 上下文的 activity 即将被销毁时调用的 activity 生命周期监听器。

Note: 该函数在 iOS 上不可用——你可能需要改用 OnAppEntersBackground

OnActivityResult

Only for:
Android

定义在使用 startActivityForResult 启动的 activity 返回结果时调用的 activity 生命周期监听器。

参数

  • activity — 接收到结果的 Android activity。
  • payload — 包含 activity 结果数据的对象。
    • requestCode: Int — 最初传递给 startActivityForResult 的请求代码,用于标识结果来源。
    • resultCode: Int — 子 activity 返回的结果代码(例如 Activity.RESULT_OKActivity.RESULT_CANCELED)。
    • data — 一个可选的 intent,携带从所启动 activity 返回的结果数据。可以为 null
Kotlin
AsyncFunction('someFunc') { %%placeholder-start%%... %%placeholder-end%% activity.startActivityForResult(someIntent, SOME_REQUEST_CODE) } OnActivityResult { activity, payload -> %%placeholder-start%%... %%placeholder-end%% }

OnNewIntent

Only for:
Android

定义在 activity 收到新 intent 时调用的 activity 生命周期监听器(例如来自深度链接)。

参数

Kotlin
OnNewIntent { intent -> val data = intent.data // 处理传入的 intent }

OnUserLeavesActivity

Only for:
Android

定义在 activity 生命周期中,当 activity 因用户选择而即将进入后台时调用的 activity 生命周期监听器。例如,当用户按下 Home 键时,会调用 OnUserLeavesActivity;但当来电导致通话中的 Activity 自动进入前台时,不会在被中断的 activity 上调用 OnUserLeavesActivity

Kotlin
OnUserLeavesActivity { // 你的实现 }

RegisterActivityContracts

Only for:
Android

注册 Android activity result contracts,使你能够启动 activity 并以类型安全的方式处理其结果。这是 startActivityForResult 的现代替代方案。

RegisterActivityContracts 块中,使用 registerForActivityResult 注册每个 contract。注册后的 launcher 可以在异步函数中用于启动 activity。

Kotlin
class ImagePickerModule : Module() { private lateinit var cameraLauncher: ActivityResultLauncher<CameraContractOptions> private lateinit var imageLibraryLauncher: ActivityResultLauncher<ImageLibraryContractOptions> override fun definition() = ModuleDefinition { Name("ImagePicker") RegisterActivityContracts { cameraLauncher = registerForActivityResult( CameraContract(this@ImagePickerModule) ) { input, result -> handleResult(result, input.options) } imageLibraryLauncher = registerForActivityResult( ImageLibraryContract(this@ImagePickerModule) ) { input, result -> handleResult(result, input.options) } } AsyncFunction("launchCameraAsync") { options: PickerOptions -> cameraLauncher.launch(CameraContractOptions(options)) } } }

View 定义组件

视图定义由描述视图功能和行为的 DSL 组件组成。这些组件只能在 View 闭包中使用。

名称

设置 JavaScript 代码用于引用该视图的名称。接受一个字符串作为参数。该名称可以从视图的类名推断出来,但建议显式设置以提高清晰度。

Swift / Kotlin
Name("MyViewName")

Prop

定义一个用于指定名称的视图属性的 setter。

参数

  • name: String — 你想要定义 setter 的视图属性名称。
  • defaultValue: ValueType — 可选的默认值,当 setter 被 null 调用时使用。
  • setter: (view: ViewType, value: ValueType) -> () — 当视图重新渲染时调用的闭包。

此属性只能在 View 闭包中使用。

Swift
Prop("background") { (view: UIView, color: UIColor) in view.backgroundColor = color }
Kotlin
Prop("background") { view: View, @ColorInt color: Int -> view.setBackgroundColor(color) }

带默认值的 Prop 定义。

Swift
Prop("background", UIColor.black) { (view: UIView, color: UIColor) in view.backgroundColor = color }
Kotlin
Prop("background", Color.BLACK) { view: View, @ColorInt color: Int -> view.setBackgroundColor(color) }

注意: 函数类型的 Props(回调)目前尚不支持。

PropGroup

Only for:
Android

批量注册多个共享相同 setter 模式的 props。你无需逐个定义每个 prop,只需使用一个处理器即可一次性注册它们。

提供两种重载:

  • 基于 Pair:每个 prop 都是一个 Pair<String, CustomValueType>。处理器接收视图、映射后的自定义值以及 prop 值。
  • 基于 String:每个 prop 都是一个名称字符串。处理器接收视图、位置索引以及 prop 值。
Kotlin
// 基于 Pair:将每个 prop 名称映射到一个自定义值 PropGroup( "borderTopColor" to LogicalEdge.TOP, "borderBottomColor" to LogicalEdge.BOTTOM, "borderLeftColor" to LogicalEdge.LEFT, "borderRightColor" to LogicalEdge.RIGHT ) { view: View, edge: LogicalEdge, color: Int? -> BackgroundStyleApplicator.setBorderColor(view, edge, color) } // 基于 String:使用位置索引 PropGroup( "borderWidth", "borderLeftWidth", "borderRightWidth", "borderTopWidth", "borderBottomWidth" ) { view: View, index: Int, width: Float? -> val edge = LogicalEdge.entries[index] BackgroundStyleApplicator.setBorderWidth(view, edge, width ?: Float.NaN) }

注意: PropGroup 在内部由 CSS prop 装饰器使用。大多数模块应使用单独的 Prop 定义,除非它们有许多共享 setter 模式的 props。

生命周期

OnViewDidUpdateProps

定义一个视图生命周期方法,当视图完成所有 props 更新后被调用。

OnViewDidUpdateProps { view: MyView in %%placeholder-start%%... %%placeholder-end%% }
OnViewDidUpdateProps { view: MyView -> %%placeholder-start%%... %%placeholder-end%% }

OnViewDestroys

Only for:
Android

创建一个视图生命周期监听器,在该视图不再被 React Native 使用后立即调用。

View(MyView::class) { OnViewDestroys { view: MyView -> %%placeholder-start%%... %%placeholder-end%% } }

注意: 此函数在 iOS 上不可用。你可能需要使用原生视图的析构函数来实现类似效果。

AsyncFunction

与模块定义中的 AsyncFunction 类似,你可以定义附加到视图 ref 的函数,以允许直接修改原生视图。

视图异步函数始终会在主队列上分发,并且可以将视图实例作为第一个参数接收。

Swift
View(MyView.self) { AsyncFunction("myAsyncFunction") { (view: MyView, message: String) in view.displayMessage(message) } }
Kotlin
View(MyView::class) { AsyncFunction("myAsyncFunction") { view: MyView, message: String -> view.displayMessage(message); } }
JavaScript
const MyNativeView = requireNativeViewManager('MyView'); function MyComponent() { const ref = React.useRef(null); React.useEffect(() => { ref.current?.myAsyncFunction(); }, [ref]); return <MyNativeView ref={ref} />; }

视图组

GroupView

Only for:
Android

使视图可作为视图组使用。被视图组定义接受的组件有:AddChildViewGetChildCountGetChildViewAtRemoveChildViewRemoveChildViewAt

参数

  • viewType — 原生视图的类。请注意,提供的类必须继承自 Android 的 ViewGroup
  • definition: () -> ViewGroupDefinition — 视图组定义的构建器。

此属性只能在 View 闭包中使用。

Kotlin
GroupView<ViewGroup> { AddChildView { parent, child, index -> %%placeholder-start%%... %%placeholder-end%%} }

AddChildView

Only for:
Android

定义将子视图添加到视图组的操作。

参数

  • action: (parent: ParentType, child: ChildType, index: Int) -> () — 一个将子视图添加到视图组的操作。

此属性只能在 GroupView 闭包中使用。

Kotlin
AddChildView { parent, child: View, index -> parent.addView(child, index) }

GetChildCount

Only for:
Android

定义获取视图组中子视图数量的操作。

参数

  • action: (parent: ParentType) -> Int — 一个返回子视图数量的函数。

此属性只能在 GroupView 闭包中使用。

Kotlin
GetChildCount { parent -> return@GetChildCount parent.childCount }

GetChildViewAt

Only for:
Android

定义从视图组中获取指定索引处子视图的操作。

参数

  • action: (parent: ParentType, index: Int) -> ChildType — 一个从视图组中获取指定索引处子视图的函数。

此属性只能在 GroupView 闭包中使用。

Kotlin
GetChildViewAt { parent, index -> parent.getChildAt(index) }

RemoveChildView

Only for:
Android

定义从视图组中移除指定子视图的操作。

参数

  • action: (parent: ParentType, child: ChildType) -> () — 一个从视图组中移除指定子视图的函数。

此属性只能在 GroupView 闭包中使用。

Kotlin
RemoveChildView { parent, child: View -> parent.removeView(child) }

RemoveChildViewAt

Only for:
Android

定义从视图组中移除指定索引处子视图的操作。

参数

  • action: (parent: ParentType, child: ChildType) -> () — 一个从视图组中移除指定索引处子视图的函数。

此属性只能在 GroupView 闭包中使用。

Kotlin
RemoveChildViewAt { parent, index -> parent.removeViewAt(child) }

参数类型

从根本上说,只有原始类型和可序列化数据才能在运行时之间来回传递。然而,原生模块通常需要接收自定义数据结构——比仅仅是值类型未知(Any)的字典/映射更复杂,因此每个值都必须单独验证和转换。Expo Modules API 提供了协议,使处理数据对象更方便,提供自动验证,并最终确保每个对象成员都具有原生类型安全。

基本类型

所有函数和视图 prop setter 都接受 Swift 和 Kotlin 中所有常见的基本类型作为参数。这包括这些基本类型的数组、字典/映射以及可选类型。

语言支持的基本类型
SwiftBool, Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64, Float32, Double, String
KotlinBoolean, Int, Long, Float, Double, String, Pair

可转换类型

可转换类型 是指可以通过从 JavaScript 接收到的某些特定数据初始化的原生类型。这类类型可以作为 Function 函数体中的参数类型使用。例如,当 CGPoint 类型被用作函数参数类型时,它的实例可以由包含两个数字 (x, y) 的数组,或带有数值 xy 属性的 JavaScript 对象创建。

内置的可转换类型在下文中有文档说明。

你可以通过让原生 Swift 类型遵循 Convertible 协议来定义额外的可转换类型:

Convertible

Only for:
iOS

Convertible 是一个 Swift 协议,只有一个静态方法:

convert(value, appContext)

ParameterTypeDescription
valueAny?

要从 JavaScript 转换的值

appContextAppContext

当前运行的 Expo 应用实例的上下文对象


一个静态方法,用于将来自 JavaScript 的动态类型值转换为遵循 Convertible 的 Swift 类型实例。如果给定值无效或类型不受支持,实作者应抛出异常。

Returns:
Self

示例

Swift
import ExpoModulesCore extension CMTime: @retroactive Convertible { public static func convert(from value: Any?, appContext: AppContext) throws -> CMTime { if let seconds = value as? Double { return CMTime(seconds: seconds, preferredTimescale: .max) } throw Conversions.ConvertingException<CMTime>(value) } }

在 Kotlin 中,无法通过协议扩展现有类型。要扩展可用类型,可以使用 ModuleConverters 构建器:

ModuleConverters

Only for:
Android

在 Android 上,模块可以定义自定义类型转换器,使非标准类型可作为函数参数使用。在你的 Module 类中重写 converters() 方法,并使用 ModuleConverters 构建器通过 .from<SourceType> { } 链注册转换器。

Kotlin
class MyModule : Module() { override fun converters() = ModuleConverters { TypeConverter(CustomType::class) .from { number: Int -> CustomType.fromInt(number) } .from { string: String -> CustomType.parse(string) } } override fun definition() = ModuleDefinition { Name("MyModule") // 现在 CustomType 可以用作参数类型 Function("process") { value: CustomType -> value.doSomething() } } }

每次 .from<T> { } 调用都会注册一个从类型 T 到你的自定义类型的转换器。运行时,框架会尝试每个已注册的转换器,直到有一个匹配传入的 JavaScript 值。

注意: 在 iOS 上,请改用上面文档中介绍的 Convertible 协议。

内置可转换类型

一些来自 CoreGraphicsUIKit 系统框架的常见 iOS 类型已经可以转换。

原生 iOS 类型TypeScript
URL带 URL 的 string。如果未提供 scheme,则默认为文件 URL。
CGFloatnumber
CGPoint{ x: number, y: number } 或包含 xy 坐标的 number[]
CGSize{ width: number, height: number } 或包含 widthheightnumber[]
CGVector{ dx: number, dy: number } 或包含 dxdy 向量差值的 number[]
CGRect{ x: number, y: number, width: number, height: number } 或包含 xywidthheight 值的 number[]
CGColor
UIColor
颜色十六进制字符串(#RRGGBB#RRGGBBAA#RGB#RGBA)、遵循 CSS3/SVG 规范 的命名颜色,或 "transparent"
DataUint8Array
SDK 50+

同样,一些来自 java.iojava.netandroid.graphics 等包的常见 Android 类型也可以转换。

注意: 在 Android 上,应尽可能使用原始数组。

原生 Android 类型TypeScript
java.net.URL带 URL 的 string。注意,必须提供 scheme(URL 不应包含任何未编码的 % 字符)
android.net.Uri
java.net.URI
带 URI 的 string。注意,必须提供 scheme(URI 不应包含任何未编码的 % 字符)
java.io.File
java.nio.file.Path (is only available on Android API 26)
指向文件路径的 string
android.graphics.Color颜色十六进制字符串(#RRGGBB#RRGGBBAA#RGB#RGBA)、遵循 CSS3/SVG 规范 的命名颜色,或 "transparent"
kotlin.Pair<A, B>包含两个值的数组,第一个值的类型为 A,第二个值的类型为 B
kotlin.ByteArrayUint8Array
SDK 50+
kotlin.BooleanArrayboolean[]
kotlin.IntArray
kotlin.FloatArray
kotlin.LongArray
kotlin.DoubleArray
number[]
kotlin.time.Duration表示秒数的 number
SDK 52+

Record

Record 是一种可转换类型,是字典(Swift)或 map(Kotlin)的等价形式,但以结构体表示,其中每个字段都可以有自己的类型并提供默认值。 它是以原生类型安全方式表示 JavaScript 对象的更好方法。

Swift
struct FileReadOptions: Record { @Field var encoding: String = "utf8" @Field var position: Int = 0 @Field var length: Int? } // 现在这个 record 可以作为函数或视图 prop setter 的参数使用。 Function("readFile") { (path: String, options: FileReadOptions) -> String in // 使用给定的 `options` 读取文件 }
Kotlin
class FileReadOptions : Record { @Field val encoding: String = "utf8" @Field val position: Int = 0 @Field val length: Int? = null } // 现在这个 record 可以作为函数或视图 prop setter 的参数使用。 Function("readFile") { path: String, options: FileReadOptions -> // 使用给定的 `options` 读取文件 }

Formatter

重要 此功能为 实验性

Formatter API 允许你自定义 Record 从原生函数返回时的序列化方式。当你需要在发送到 JavaScript 之前转换属性值,或有条件地从输出中排除某些属性时,这会很有用。

操作

  • map:在序列化前转换属性值。
  • skip:完全从输出中排除某个属性。

基本用法

Swift
struct UserInfo: Record { @Field var id: Int = 0 @Field var email: String = "" @Field var password: String = "" } Function("getUser") { let user = UserInfo(id: 1, email: "user@example.com", password: "secret123") // 返回不暴露密码的用户 return user.format { formatter in formatter.property("password", keyPath: \.password).skip() } }
Kotlin
class UserInfo( @Field val id: Int = 0, @Field val email: String = "", @Field val password: String = "" ) : Record Function("getUser") { val user = UserInfo(id = 1, email = "user@example.com", password = "secret123") // 返回不暴露密码的用户 formatter { property(UserInfo::password).skip() }.format(user) }
JavaScript
const user = MyModule.getUser(); console.log(user); // 输出:{ id: 1, email: "user@example.com" } // 注意:对象中不包含 password

使用 map 转换值

使用 map 在属性发送到 JavaScript 之前转换它们的值:

Swift
struct Product: Record { @Field var name: String = "" @Field var price: Double = 0.0 } Function("getProduct") { let product = Product(name: "Widget", price: 19.99) return product.format { formatter in // 将价格转换为带货币符号的字符串 formatter.property("price", keyPath: \.price).map { value in "$\(String(format: "%.2f", value))" } } }
Kotlin
class Product( @Field val name: String = "", @Field val price: Double = 0.0 ) : Record Function("getProduct") { val product = Product(name = "Widget", price = 19.99) formatter { // 将价格转换为带货币符号的字符串 property(Product::price).map { value -> "${"$"}${String.format("%.2f", value)}" } }.format(product) }

条件性跳过

你可以根据属性值或 record 的状态有条件地跳过属性:

Swift
struct Settings: Record { @Field var theme: String = "light" @Field var debugMode: Bool = false @Field var apiKey: String? = nil } Function("getSettings") { let settings = Settings(theme: "dark", debugMode: true, apiKey: "secret") return settings.format { formatter in // 如果 apiKey 为 nil 则跳过 formatter.property("apiKey", keyPath: \.apiKey).skip { value in value == nil } } }
Kotlin
class Settings( @Field val theme: String = "light", @Field val debugMode: Boolean = false, @Field val apiKey: String? = null ) : Record Function("getSettings") { val settings = Settings(theme = "dark", debugMode = true, apiKey = "secret") formatter { // 如果 apiKey 为 null 则跳过 property(Settings::apiKey).skip { value -> value == null } }.format(settings) }

链式操作

你可以在同一个属性上链式执行多个操作:

Swift
struct Data: Record { @Field var value: Int? = nil } Function("getData") { let data = Data(value: nil) return data.format { formatter in formatter.property("value", keyPath: \.value) .map { $0 ?? 0 } // 如果为 nil,则默认为 0 .map { $0 * 2 } // 将值翻倍 } }
Kotlin
class Data( @Field val value: Int? = null ) : Record Function("getData") { val data = Data(value = null) formatter { property(Data::value) .map { it ?: 0 } // 如果为 null,则默认为 0 .map { it * 2 } // 将值翻倍 }.format(data) }

枚举

使用枚举时,我们可以在上面的示例(使用 FileReadOptions record)基础上更进一步,将支持的编码限制为 "utf8""base64"。要将枚举用作参数或 record 字段,它必须表示一个基本值(例如 StringInt),并且符合 Enumerable

Swift
enum FileEncoding: String, Enumerable { case utf8 case base64 } struct FileReadOptions: Record { @Field var encoding: FileEncoding = .utf8 %%placeholder-start%%... %%placeholder-end%% }
Kotlin
// 注意:构造函数必须有一个名为 value 的参数。 enum class FileEncoding(val value: String) : Enumerable { utf8("utf8"), base64("base64") } class FileReadOptions : Record { @Field val encoding: FileEncoding = FileEncoding.utf8 %%placeholder-start%%... %%placeholder-end%% }

Either 类型

有些使用场景需要为同一个函数参数传递多种类型。这时 Either 类型就派上用场了。 它们充当一个容器,用于存放若干类型之一的值。

Swift
Function("foo") { (bar: Either<String, Int>) in if let bar: String = bar.get() { // `bar` 是一个 String } if let bar: Int = bar.get() { // `bar` 是一个 Int } }
Kotlin
Function("foo") { bar: Either<String, Int> -> bar.get(String::class).let { // `it` 是一个 String } bar.get(Int::class).let { // `it` 是一个 Int } }

目前已内置三种 Either 类型的实现,因此你最多可以使用四种不同的子类型。

  • Either<FirstType, SecondType> — 一个容纳两种类型之一的容器。
  • EitherOfThree<FirstType, SecondType, ThirdType> — 一个容纳三种类型之一的容器。
  • EitherOfFour<FirstType, SecondType, ThirdType, FourthType> — 一个容纳四种类型之一的容器。

ValueOrUndefined

重要 此功能为 实验性

ValueOrUndefined 是一种包装类型,允许你区分 JavaScript 的 undefined 值和实际值。

对于普通可选类型,JavaScript 中的 undefinednull 在原生端都会被转换为 null,因此无法区分它们。ValueOrUndefined 通过保留这种区别来解决这个问题。

属性

isUndefined

如果 JavaScript 值是 undefined,则返回 true,否则返回 false

Returns:
Bool

optional

如果存在则返回解包后的值;如果值是 undefined,则返回 null

Returns:
InnerType?
Swift
Function("configure") { (timeout: ValueOrUndefined<Int>) in if timeout.isUndefined { // 参数未提供,使用默认行为 } else if let value = timeout.optional { // 参数已提供且带有值 } }
Kotlin
Function("configure") { timeout: ValueOrUndefined<Int> -> if (timeout.isUndefined) { // 参数未提供,使用默认行为 } else { timeout.optional?.let { value -> // 参数已提供且带有值 } } }

区分 undefinednull

ValueOrUndefined 与可选内部类型一起使用时,你可以区分三种状态:

Swift
Function("setName") { (name: ValueOrUndefined<String?>) in switch name { case .undefined: // name 参数未提供 break case .value(let unwrapped) where unwrapped == nil: // name 被显式设置为 null break case .value(let unwrapped): // name 被设置为字符串值 print("Name: \(unwrapped!)") } }
Kotlin
Function("setName") { name: ValueOrUndefined<String?> -> when { name.isUndefined -> { // name 参数未提供 } name.optional == null -> { // name 被显式设置为 null } else -> { // name 被设置为字符串值 println("Name: ${name.optional}") } } }
JavaScript
import { requireNativeModule } from 'expo-modules-core'; const MyModule = requireNativeModule('MyModule'); MyModule.setName('Alice'); // name 是一个值 MyModule.setName(null); // name 是 null(但不是 undefined) MyModule.setName(undefined); // name 是 undefined

JavaScript 值

也可以使用 JavaScriptValue 类型,它是任何可以在 JavaScript 中表示的值的持有器。 当你想要修改给定参数,或者想要省略类型验证和转换时,这种类型很有用。 请注意,使用 JavaScript 专有类型仅限于同步函数,因为 JavaScript 运行时中的所有读写都必须发生在 JavaScript 线程上。 从不同线程访问这些值会导致崩溃。

除了原始值之外,还可以使用 JavaScriptObject 类型来仅允许对象类型,以及 JavaScriptFunction<ReturnType> 用于回调。

Swift
Function("mutateMe") { (value: JavaScriptValue) in if value.isObject() { let jsObject = value.getObject() jsObject.setProperty("expo", value: "modules") } } // or Function("mutateMe") { (jsObject: JavaScriptObject) in jsObject.setProperty("expo", value: "modules") }
Kotlin
Function("mutateMe") { value: JavaScriptValue -> if (value.isObject()) { val jsObject = value.getObject() jsObject.setProperty("expo", "modules") } } // or Function("mutateMe") { jsObject: JavaScriptObject -> jsObject.setProperty("expo", "modules") }

原生类

Module

原生模块的基类。

属性

appContext

提供对 AppContext 的访问。

Returns:
AppContext

方法

sendEvent(eventName, payload)

ParameterTypeDescription
eventNamestring

JavaScript 事件的名称

payloadAndroid: Map<String, Any?> | Bundle iOS: [String: Any?]

事件负载


向 JavaScript 发送一个具有给定名称和负载的事件。参见 发送事件

Returns:
void

AppContext

应用上下文是一个面向单个 Expo 应用的接口。

属性

constants

提供对旧版模块注册表中应用常量的访问。

Returns:
Android: ConstantsInterface? iOS: EXConstantsInterface?

permissions

提供对旧版模块注册表中权限管理器的访问。

Returns:
Android: Permissions? iOS: EXPermissionsInterface?

activityProvider

提供对旧版模块注册表中 activity provider 的访问。

Returns:
ActivityProvider?

reactContext

提供对 react 应用上下文的访问。

Returns:
Context?

hasActiveReactInstance

检查是否存在一个非空且存活的 react native 实例。

Returns:
Boolean

utilities

提供对旧版模块注册表中工具集的访问。

Returns:
EXUtilitiesInterface?

ExpoView

所有导出的视图都应使用的基类。

在 iOS 上,ExpoView 继承自 RCTView,它处理了一些样式(例如边框)和可访问性。

属性

appContext

提供对 AppContext 的访问。

Returns:
AppContext

扩展 ExpoView

要使用 View 组件导出你的视图,自定义类必须继承 ExpoView。这样你就能访问 AppContext 对象。它是与其他模块和 JavaScript 运行时通信的唯一方式。另外,你不能更改构造函数参数,因为提供的视图将由 expo-modules-core 初始化。

Swift
class LinearGradientView: ExpoView {} public class LinearGradientModule: Module { public func definition() -> ModuleDefinition { View(LinearGradientView.self) { %%placeholder-start%%... %%placeholder-end%% } } }
Kotlin
class LinearGradientView( context: Context, appContext: AppContext, ) : ExpoView(context, appContext) class LinearGradientModule : Module() { override fun definition() = ModuleDefinition { View(LinearGradientView::class) { %%placeholder-start%%... %%placeholder-end%% } } }

指南

发送事件

虽然 JavaScript/TypeScript 到原生的通信大多由原生函数涵盖,但你可能也希望让 JavaScript/TypeScript 代码了解某些系统事件,例如剪贴板内容发生变化时。

为此,在模块定义中,你需要使用 Events 定义组件提供模块可以发送的事件名称。之后,你可以在模块实例上使用 sendEvent(eventName, payload) 函数发送带有某些负载的实际事件。例如,一个发送原生事件的最小剪贴板实现可能如下所示:

Swift
let CLIPBOARD_CHANGED_EVENT_NAME = "onClipboardChanged" public class ClipboardModule: Module { public func definition() -> ModuleDefinition { Events(CLIPBOARD_CHANGED_EVENT_NAME) OnStartObserving { NotificationCenter.default.addObserver( self, selector: #selector(self.clipboardChangedListener), name: UIPasteboard.changedNotification, object: nil ) } OnStopObserving { NotificationCenter.default.removeObserver( self, name: UIPasteboard.changedNotification, object: nil ) } } @objc private func clipboardChangedListener() { sendEvent(CLIPBOARD_CHANGED_EVENT_NAME, [ "contentTypes": availableContentTypes() ]) } }
Kotlin
const val CLIPBOARD_CHANGED_EVENT_NAME = "onClipboardChanged" class ClipboardModule : Module() { override fun definition() = ModuleDefinition { Events(CLIPBOARD_CHANGED_EVENT_NAME) OnStartObserving { clipboardManager?.addPrimaryClipChangedListener(listener) } OnStopObserving { clipboardManager?.removePrimaryClipChangedListener(listener) } } private val clipboardManager: ClipboardManager? get() = appContext.reactContext?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager private val listener = ClipboardManager.OnPrimaryClipChangedListener { clipboardManager?.primaryClipDescription?.let { clip -> this@ClipboardModule.sendEvent( CLIPBOARD_CHANGED_EVENT_NAME, bundleOf( "contentTypes" to availableContentTypes(clip) ) ) } } }

要在 JavaScript/TypeScript 中订阅这些事件,请在 requireNativeModule 返回的模块对象上使用 addListener。模块继承了内置的 EventEmitter 类。 或者,你也可以使用 useEventuseEventListener hooks。

TypeScript
import { requireNativeModule, NativeModule } from 'expo'; type ClipboardChangeEvent = { contentTypes: string[]; }; type ClipboardModuleEvents = { onClipboardChanged(event: ClipboardChangeEvent): void; }; declare class ClipboardModule extends NativeModule<ClipboardModuleEvents> {} const Clipboard = requireNativeModule<ClipboardModule>('Clipboard'); Clipboard.addListener('onClipboardChanged', (event: ClipboardChangeEvent) => { alert('剪贴板已更改'); });

视图回调

某些事件与特定视图相关联。例如,触摸事件应仅发送到被按下的底层 JavaScript 视图。在这种情况下,你不能使用在 发送事件 中描述的 sendEventexpo-modules-core 引入了一种视图回调机制来处理绑定到视图的事件。

要使用它,在视图定义中,你需要使用 Events 定义组件提供视图可以发送的事件名称。之后,你需要在视图类中声明一个 EventDispatcher 类型的属性。所声明属性的名称必须与 Events 组件中导出的名称相同。之后,你可以把它当作函数调用,并在 iOS 上传入类型为 [String: Any?] 的负载,在 Android 上传入类型为 Map<String, Any?> 的负载。

注意:: 在 Android 上,可以指定负载类型。对于无法转换为对象的类型,负载将被封装并存储在 payload 键下:{payload: <provided value>}

Swift
class CameraViewModule: Module { public func definition() -> ModuleDefinition { View(CameraView.self) { Events( "onCameraReady" ) %%placeholder-start%%... %%placeholder-end%% } } } class CameraView: ExpoView { let onCameraReady = EventDispatcher() func callOnCameraReady() { onCameraReady([ "message": "Camera was mounted" ]); } }
Kotlin
class CameraViewModule : Module() { override fun definition() = ModuleDefinition { View(ExpoCameraView::class) { Events( "onCameraReady" ) %%placeholder-start%%... %%placeholder-end%% } } } class CameraView( context: Context, appContext: AppContext ) : ExpoView(context, appContext) { val onCameraReady by EventDispatcher() fun callOnCameraReady() { onCameraReady(mapOf( "message" to "Camera was mounted" )); } }

要在 JavaScript/TypeScript 中订阅这些事件,你需要像下面这样向原生视图传递一个函数:

TypeScript
import { requireNativeViewManager } from 'expo-modules-core'; const CameraView = requireNativeViewManager('CameraView'); export default function MainView() { const onCameraReady = event => { console.log(event.nativeEvent); }; return <CameraView onCameraReady={onCameraReady} />; }

提供的负载可通过 nativeEvent 键获取。

示例

Swift
public class MyModule: Module { public func definition() -> ModuleDefinition { Name("MyFirstExpoModule") Function("hello") { (name: String) in return "Hello \(name)!" } } }
Kotlin
class MyModule : Module() { override fun definition() = ModuleDefinition { Name("MyFirstExpoModule") Function("hello") { name: String -> return "Hello $name!" } } }

如需查看更多真实模块的示例,你可以参考 GitHub 上已经使用此 API 的 Expo 模块:

expo-batterySwift
expo-cellular

Kotlin, Swift

expo-clipboard

Kotlin, Swift

expo-crypto

Kotlin, Swift

expo-deviceSwift
expo-hapticsSwift
expo-image-manipulatorSwift
expo-image-picker

Kotlin, Swift

expo-linear-gradient

Kotlin, Swift

expo-localization

Kotlin, Swift

expo-store-reviewSwift
expo-system-uiSwift
expo-video-thumbnailsSwift
expo-web-browser

Kotlin, Swift