模块 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 代码将用于引用该模块的模块名称。接受一个字符串作为参数。该名称可以从模块的类名推断出来,但为清晰起见,建议显式设置。
Name("MyModuleName")
常量
定义 JavaScript 对象上的一个常量属性。该属性仅在首次访问时计算一次,之后的访问会返回缓存值。
Constant("PI") { Double.pi }
Constant("PI") { Math.PI }
常量集合
为模块设置常量属性。可以接受一个字典或一个返回字典的闭包。
// 从字典创建 Constants([ "PI": Double.pi ]) // 或由闭包返回 Constants { return [ "PI": Double.pi ] }
// 作为参数传入 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 中泛型的限制,因为该组件必须针对每种参数个数分别实现。
有关函数体中可使用哪些类型的更多详情,请参阅 参数类型 部分。
Function("mySyncFunction") { (message: String) in return message }
Function("mySyncFunction") { message: String -> return@Function message }
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 线程,从而降低应用响应速度
AsyncFunction("myAsyncFunction") { (message: String) in return message } // or AsyncFunction("myAsyncFunction") { (message: String, promise: Promise) in promise.resolve(message) }
AsyncFunction("myAsyncFunction") { message: String -> return@AsyncFunction message } // or // 确保从 `expo.modules.kotlin` 导入 `Promise` 类,而不是 `expo.modules.core`。 AsyncFunction("myAsyncFunction") { message: String, promise: Promise -> promise.resolve(message) }
import { requireNativeModule } from 'expo-modules-core'; // 假设我们将模块命名为 "MyModule" const MyModule = requireNativeModule('MyModule'); async function getMessageAsync() { return await MyModule.myAsyncFunction('bar'); }
可以通过在该组件的返回结果上调用 .runOnQueue 函数来更改 AsyncFunction 的本地队列。
AsyncFunction("myAsyncFunction") { (message: String) in return message }.runOnQueue(.main)
AsyncFunction("myAsyncFunction") { message: String -> return@AsyncFunction message }.runOnQueue(Queues.MAIN)
Kotlin 协程 Android
AsyncFunction 在 Android 上可以接收可挂起的主体。不过,它必须在 Coroutine 块之后以中缀表示法传入。你可以在 协程概览 中了解更多关于可挂起函数和协程的信息。
带有可挂起主体的 AsyncFunction 不能将 Promise 作为参数接收。它使用挂起机制来执行异步调用。
该函数会立即以所提供的可挂起块的返回值 resolve,或者在抛出异常时 reject。该函数最多可接收 8 个参数。
默认情况下,挂起函数会在模块的协程作用域中派发。此外,从主体块中调用的其他任何可挂起函数也会在同一作用域内运行。 该作用域的生命周期绑定到模块的生命周期——所有未完成的挂起函数都会在模块被释放时被取消。
AsyncFunction("suspendFunction") Coroutine { message: String -> // 你可以在这里执行其他可挂起函数。 // 例如,你可以使用 `kotlinx.coroutines.delay` 来延迟底层 promise 的解析。 delay(5000) return@Coroutine message }
属性
直接在表示本机模块的 JavaScript 对象上定义一个新属性。它与在模块对象上调用 Object.defineProperty 相同。
要声明一个只读属性,你可以使用一种简写语法,需要两个参数:
- name:
String— 你将在 JavaScript 中使用的属性名称。 - getter:
() -> PropertyType— 属性 getter 被调用时运行的闭包。
Property("foo") { return "bar" }
Property("foo") { return@Property "bar" }
对于可变属性,需要同时提供 getter 和 setter 闭包(使用下面的语法也可以声明只有 setter 的属性):
- name:
String— 你将在 JavaScript 中使用的属性名称。 - getter:
() -> PropertyType— 属性 getter 被调用时运行的闭包。 - setter:
(newValue: PropertyType) -> void— 属性 setter 被调用时运行的闭包。
Property("foo") .get { return "bar" } .set { (newValue: String) in // 使用新值做一些事情 }
Property("foo") .get { return@get "bar" } .set { newValue: String -> // 使用新值做一些事情 }
import { requireNativeModule } from 'expo-modules-core'; // 假设我们将模块命名为 "MyModule" const MyModule = requireNativeModule('MyModule'); // 获取属性值 MyModule.foo; // 设置新值 MyModule.foo = 'foobar';
视图
允许将模块用作本机视图。作为视图定义一部分接受的定义组件有:Prop、Events、GroupView 和 AsyncFunction。
视图定义中的 AsyncFunction 会添加到表示本机视图的 React 组件的 React ref 上。
这样的异步函数会自动将本机视图的实例作为第一个参数接收,并默认在 UI 线程上运行。
参数
- viewType — 将被渲染的本机视图类。注意:在 Android 上,提供的类必须继承自
ExpoView;在 iOS 上则是可选的。参见Extending ExpoView。 - definition:
() -> ViewDefinition— 视图定义的构建器。
View(UITextView.self) { Prop("text") { %%placeholder-start%%... %%placeholder-end%% } AsyncFunction("focus") { (view: UITextView) in view.becomeFirstResponder() } }
View(TextView::class) { Prop("text") { %%placeholder-start%%... %%placeholder-end%% } AsyncFunction("focus") { view: TextView -> view.requestFocus() } }
支持渲染 SwiftUI 视图的功能正在计划中。现在,你可以使用UIHostingController并将其内容视图添加到你的 UIKit 视图中。
事件监听
事件
定义模块可以发送给 JavaScript 的事件名称。
Note: 该组件可在
View块内使用,以定义回调名称。参见View callbacks
Events("onCameraReady", "onPictureSaved", "onBarCodeScanned")
Events("onCameraReady", "onPictureSaved", "onBarCodeScanned")
请参阅 发送事件 了解如何从本地代码向 JavaScript/TypeScript 发送事件。
OnStartObserving
定义在添加第一个事件监听器时调用的函数。
你需要传入一个事件名称,以将监听器限定到特定事件。这在你需要按事件而不是全局地设置或清理资源时很有用。
// 在添加 "onURLReceived" 的监听器时调用 OnStartObserving("onURLReceived") { %%placeholder-start%%... %%placeholder-end%% }
// 在添加 "onURLReceived" 的监听器时调用 OnStartObserving("onURLReceived") { %%placeholder-start%%... %%placeholder-end%% }
OnStopObserving
定义在移除某个给定事件的所有事件监听器时调用的函数。
与 OnStartObserving 类似,你需要传入一个事件名称,以将监听器限定到特定事件。
// 在移除 "onURLReceived" 的监听器时调用 OnStopObserving("onURLReceived") { %%placeholder-start%%... %%placeholder-end%% }
// 在移除 "onURLReceived" 的监听器时调用 OnStopObserving("onURLReceived") { %%placeholder-start%%... %%placeholder-end%% }
生命周期监听器
OnAppEntersForeground
定义在应用即将进入前台模式时调用的监听器。
Note: 该函数在 Android 上不可用——你可能需要改用
OnActivityEntersForeground。
OnAppEntersBackground
定义在应用进入后台模式时调用的监听器。
Note: 该函数在 Android 上不可用——你可能需要改用
OnActivityEntersBackground。
OnAppBecomesActive
定义在应用再次变为活动状态时调用的监听器(在 OnAppEntersForeground 之后)。
Note: 该函数在 Android 上不可用——你可能需要改用
OnActivityEntersForeground。
OnActivityEntersForeground
定义在 activity 恢复后立即调用的 activity 生命周期监听器。
Note: 该函数在 iOS 上不可用——你可能需要改用
OnAppEntersForeground。
OnActivityEntersBackground
定义在 activity 暂停后立即调用的 activity 生命周期监听器。
Note: 该函数在 iOS 上不可用——你可能需要改用
OnAppEntersBackground。
OnActivityDestroys
定义在拥有 JavaScript 上下文的 activity 即将被销毁时调用的 activity 生命周期监听器。
Note: 该函数在 iOS 上不可用——你可能需要改用
OnAppEntersBackground。
OnActivityResult
定义在使用 startActivityForResult 启动的 activity 返回结果时调用的 activity 生命周期监听器。
参数
- activity — 接收到结果的 Android activity。
- payload — 包含 activity 结果数据的对象。
- requestCode:
Int— 最初传递给startActivityForResult的请求代码,用于标识结果来源。 - resultCode:
Int— 子 activity 返回的结果代码(例如Activity.RESULT_OK或Activity.RESULT_CANCELED)。 - data — 一个可选的 intent,携带从所启动 activity 返回的结果数据。可以为
null。
- requestCode:
AsyncFunction('someFunc') { %%placeholder-start%%... %%placeholder-end%% activity.startActivityForResult(someIntent, SOME_REQUEST_CODE) } OnActivityResult { activity, payload -> %%placeholder-start%%... %%placeholder-end%% }
OnNewIntent
定义在 activity 收到新 intent 时调用的 activity 生命周期监听器(例如来自深度链接)。
参数
- intent:
Intent— 新 intent 已交付给该 activity。有关Intent类型的更多信息,请访问:https://developer.android.com/reference/android/content/Intent。
OnNewIntent { intent -> val data = intent.data // 处理传入的 intent }
OnUserLeavesActivity
定义在 activity 生命周期中,当 activity 因用户选择而即将进入后台时调用的 activity 生命周期监听器。例如,当用户按下 Home 键时,会调用 OnUserLeavesActivity;但当来电导致通话中的 Activity 自动进入前台时,不会在被中断的 activity 上调用 OnUserLeavesActivity。
OnUserLeavesActivity { // 你的实现 }
RegisterActivityContracts
注册 Android activity result contracts,使你能够启动 activity 并以类型安全的方式处理其结果。这是 startActivityForResult 的现代替代方案。
在 RegisterActivityContracts 块中,使用 registerForActivityResult 注册每个 contract。注册后的 launcher 可以在异步函数中用于启动 activity。
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 代码用于引用该视图的名称。接受一个字符串作为参数。该名称可以从视图的类名推断出来,但建议显式设置以提高清晰度。
Name("MyViewName")
Prop
定义一个用于指定名称的视图属性的 setter。
参数
- name:
String— 你想要定义 setter 的视图属性名称。 - defaultValue:
ValueType— 可选的默认值,当 setter 被null调用时使用。 - setter:
(view: ViewType, value: ValueType) -> ()— 当视图重新渲染时调用的闭包。
此属性只能在 View 闭包中使用。
Prop("background") { (view: UIView, color: UIColor) in view.backgroundColor = color }
Prop("background") { view: View, @ColorInt color: Int -> view.setBackgroundColor(color) }
带默认值的 Prop 定义。
Prop("background", UIColor.black) { (view: UIView, color: UIColor) in view.backgroundColor = color }
Prop("background", Color.BLACK) { view: View, @ColorInt color: Int -> view.setBackgroundColor(color) }
注意: 函数类型的 Props(回调)目前尚不支持。
PropGroup
批量注册多个共享相同 setter 模式的 props。你无需逐个定义每个 prop,只需使用一个处理器即可一次性注册它们。
提供两种重载:
- 基于 Pair:每个 prop 都是一个
Pair<String, CustomValueType>。处理器接收视图、映射后的自定义值以及 prop 值。 - 基于 String:每个 prop 都是一个名称字符串。处理器接收视图、位置索引以及 prop 值。
// 基于 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
创建一个视图生命周期监听器,在该视图不再被 React Native 使用后立即调用。
View(MyView::class) { OnViewDestroys { view: MyView -> %%placeholder-start%%... %%placeholder-end%% } }
注意: 此函数在 iOS 上不可用。你可能需要使用原生视图的析构函数来实现类似效果。
AsyncFunction
与模块定义中的 AsyncFunction 类似,你可以定义附加到视图 ref 的函数,以允许直接修改原生视图。
视图异步函数始终会在主队列上分发,并且可以将视图实例作为第一个参数接收。
View(MyView.self) { AsyncFunction("myAsyncFunction") { (view: MyView, message: String) in view.displayMessage(message) } }
View(MyView::class) { AsyncFunction("myAsyncFunction") { view: MyView, message: String -> view.displayMessage(message); } }
const MyNativeView = requireNativeViewManager('MyView'); function MyComponent() { const ref = React.useRef(null); React.useEffect(() => { ref.current?.myAsyncFunction(); }, [ref]); return <MyNativeView ref={ref} />; }
视图组
GroupView
使视图可作为视图组使用。被视图组定义接受的组件有:AddChildView、GetChildCount、GetChildViewAt、RemoveChildView、RemoveChildViewAt。
参数
- viewType — 原生视图的类。请注意,提供的类必须继承自 Android 的
ViewGroup。 - definition:
() -> ViewGroupDefinition— 视图组定义的构建器。
此属性只能在 View 闭包中使用。
GroupView<ViewGroup> { AddChildView { parent, child, index -> %%placeholder-start%%... %%placeholder-end%%} }
AddChildView
定义将子视图添加到视图组的操作。
参数
- action:
(parent: ParentType, child: ChildType, index: Int) -> ()— 一个将子视图添加到视图组的操作。
此属性只能在 GroupView 闭包中使用。
AddChildView { parent, child: View, index -> parent.addView(child, index) }
GetChildCount
定义获取视图组中子视图数量的操作。
参数
- action:
(parent: ParentType) -> Int— 一个返回子视图数量的函数。
此属性只能在 GroupView 闭包中使用。
GetChildCount { parent -> return@GetChildCount parent.childCount }
GetChildViewAt
定义从视图组中获取指定索引处子视图的操作。
参数
- action:
(parent: ParentType, index: Int) -> ChildType— 一个从视图组中获取指定索引处子视图的函数。
此属性只能在 GroupView 闭包中使用。
GetChildViewAt { parent, index -> parent.getChildAt(index) }
RemoveChildView
定义从视图组中移除指定子视图的操作。
参数
- action:
(parent: ParentType, child: ChildType) -> ()— 一个从视图组中移除指定子视图的函数。
此属性只能在 GroupView 闭包中使用。
RemoveChildView { parent, child: View -> parent.removeView(child) }
RemoveChildViewAt
定义从视图组中移除指定索引处子视图的操作。
参数
- action:
(parent: ParentType, child: ChildType) -> ()— 一个从视图组中移除指定索引处子视图的函数。
此属性只能在 GroupView 闭包中使用。
RemoveChildViewAt { parent, index -> parent.removeViewAt(child) }
参数类型
从根本上说,只有原始类型和可序列化数据才能在运行时之间来回传递。然而,原生模块通常需要接收自定义数据结构——比仅仅是值类型未知(Any)的字典/映射更复杂,因此每个值都必须单独验证和转换。Expo Modules API 提供了协议,使处理数据对象更方便,提供自动验证,并最终确保每个对象成员都具有原生类型安全。
基本类型
所有函数和视图 prop setter 都接受 Swift 和 Kotlin 中所有常见的基本类型作为参数。这包括这些基本类型的数组、字典/映射以及可选类型。
| 语言 | 支持的基本类型 |
|---|---|
| Swift | Bool, Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64, Float32, Double, String |
| Kotlin | Boolean, Int, Long, Float, Double, String, Pair |
可转换类型
可转换类型 是指可以通过从 JavaScript 接收到的某些特定数据初始化的原生类型。这类类型可以作为 Function 函数体中的参数类型使用。例如,当 CGPoint 类型被用作函数参数类型时,它的实例可以由包含两个数字 (x, y) 的数组,或带有数值 x 和 y 属性的 JavaScript 对象创建。
内置的可转换类型在下文中有文档说明。
你可以通过让原生 Swift 类型遵循 Convertible 协议来定义额外的可转换类型:
Convertible
Convertible 是一个 Swift 协议,只有一个静态方法:
| Parameter | Type | Description |
|---|---|---|
| value | Any? | 要从 JavaScript 转换的值 |
| appContext | AppContext | 当前运行的 Expo 应用实例的上下文对象 |
一个静态方法,用于将来自 JavaScript 的动态类型值转换为遵循 Convertible 的 Swift 类型实例。如果给定值无效或类型不受支持,实作者应抛出异常。
Self示例
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
在 Android 上,模块可以定义自定义类型转换器,使非标准类型可作为函数参数使用。在你的 Module 类中重写 converters() 方法,并使用 ModuleConverters 构建器通过 .from<SourceType> { } 链注册转换器。
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协议。
内置可转换类型
一些来自 CoreGraphics 和 UIKit 系统框架的常见 iOS 类型已经可以转换。
| 原生 iOS 类型 | TypeScript |
|---|---|
URL | 带 URL 的 string。如果未提供 scheme,则默认为文件 URL。 |
CGFloat | number |
CGPoint | { x: number, y: number } 或包含 x 和 y 坐标的 number[] |
CGSize | { width: number, height: number } 或包含 width 和 height 的 number[] |
CGVector | { dx: number, dy: number } 或包含 dx 和 dy 向量差值的 number[] |
CGRect | { x: number, y: number, width: number, height: number } 或包含 x、y、width 和 height 值的 number[] |
CGColorUIColor | 颜色十六进制字符串(#RRGGBB、#RRGGBBAA、#RGB、#RGBA)、遵循 CSS3/SVG 规范 的命名颜色,或 "transparent" |
Data | Uint8Array SDK 50+ |
同样,一些来自 java.io、java.net 或 android.graphics 等包的常见 Android 类型也可以转换。
注意: 在 Android 上,应尽可能使用原始数组。
| 原生 Android 类型 | TypeScript |
|---|---|
java.net.URL | 带 URL 的 string。注意,必须提供 scheme(URL 不应包含任何未编码的 % 字符) |
android.net.Urijava.net.URI | 带 URI 的 string。注意,必须提供 scheme(URI 不应包含任何未编码的 % 字符) |
java.io.Filejava.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.ByteArray | Uint8Array SDK 50+ |
kotlin.BooleanArray | boolean[] |
kotlin.IntArraykotlin.FloatArraykotlin.LongArraykotlin.DoubleArray | number[] |
kotlin.time.Duration | 表示秒数的 number SDK 52+ |
Record
Record 是一种可转换类型,是字典(Swift)或 map(Kotlin)的等价形式,但以结构体表示,其中每个字段都可以有自己的类型并提供默认值。 它是以原生类型安全方式表示 JavaScript 对象的更好方法。
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` 读取文件 }
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:完全从输出中排除某个属性。
基本用法
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() } }
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) }
const user = MyModule.getUser(); console.log(user); // 输出:{ id: 1, email: "user@example.com" } // 注意:对象中不包含 password
使用 map 转换值
使用 map 在属性发送到 JavaScript 之前转换它们的值:
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))" } } }
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 的状态有条件地跳过属性:
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 } } }
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) }
链式操作
你可以在同一个属性上链式执行多个操作:
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 } // 将值翻倍 } }
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 字段,它必须表示一个基本值(例如 String、Int),并且符合 Enumerable。
enum FileEncoding: String, Enumerable { case utf8 case base64 } struct FileReadOptions: Record { @Field var encoding: FileEncoding = .utf8 %%placeholder-start%%... %%placeholder-end%% }
// 注意:构造函数必须有一个名为 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 类型就派上用场了。 它们充当一个容器,用于存放若干类型之一的值。
Function("foo") { (bar: Either<String, Int>) in if let bar: String = bar.get() { // `bar` 是一个 String } if let bar: Int = bar.get() { // `bar` 是一个 Int } }
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 中的 undefined 和 null 在原生端都会被转换为 null,因此无法区分它们。ValueOrUndefined 通过保留这种区别来解决这个问题。
属性
Function("configure") { (timeout: ValueOrUndefined<Int>) in if timeout.isUndefined { // 参数未提供,使用默认行为 } else if let value = timeout.optional { // 参数已提供且带有值 } }
Function("configure") { timeout: ValueOrUndefined<Int> -> if (timeout.isUndefined) { // 参数未提供,使用默认行为 } else { timeout.optional?.let { value -> // 参数已提供且带有值 } } }
区分 undefined 与 null
当 ValueOrUndefined 与可选内部类型一起使用时,你可以区分三种状态:
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!)") } }
Function("setName") { name: ValueOrUndefined<String?> -> when { name.isUndefined -> { // name 参数未提供 } name.optional == null -> { // name 被显式设置为 null } else -> { // name 被设置为字符串值 println("Name: ${name.optional}") } } }
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> 用于回调。
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") }
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方法
| Parameter | Type | Description |
|---|---|---|
| eventName | string | JavaScript 事件的名称 |
| payload | Android: Map<String, Any?> | Bundle
iOS: [String: Any?] | 事件负载 |
向 JavaScript 发送一个具有给定名称和负载的事件。参见 发送事件
voidExpoView
所有导出的视图都应使用的基类。
在 iOS 上,ExpoView 继承自 RCTView,它处理了一些样式(例如边框)和可访问性。
属性
提供对 AppContext 的访问。
AppContext扩展 ExpoView
要使用 View 组件导出你的视图,自定义类必须继承 ExpoView。这样你就能访问 AppContext 对象。它是与其他模块和 JavaScript 运行时通信的唯一方式。另外,你不能更改构造函数参数,因为提供的视图将由 expo-modules-core 初始化。
class LinearGradientView: ExpoView {} public class LinearGradientModule: Module { public func definition() -> ModuleDefinition { View(LinearGradientView.self) { %%placeholder-start%%... %%placeholder-end%% } } }
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) 函数发送带有某些负载的实际事件。例如,一个发送原生事件的最小剪贴板实现可能如下所示:
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() ]) } }
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 类。
或者,你也可以使用 useEvent 或 useEventListener hooks。
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 视图。在这种情况下,你不能使用在 发送事件 中描述的 sendEvent。expo-modules-core 引入了一种视图回调机制来处理绑定到视图的事件。
要使用它,在视图定义中,你需要使用 Events 定义组件提供视图可以发送的事件名称。之后,你需要在视图类中声明一个 EventDispatcher 类型的属性。所声明属性的名称必须与 Events 组件中导出的名称相同。之后,你可以把它当作函数调用,并在 iOS 上传入类型为 [String: Any?] 的负载,在 Android 上传入类型为 Map<String, Any?> 的负载。
注意:: 在 Android 上,可以指定负载类型。对于无法转换为对象的类型,负载将被封装并存储在
payload键下:{payload: <provided value>}。
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" ]); } }
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 中订阅这些事件,你需要像下面这样向原生视图传递一个函数:
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 键获取。
示例
public class MyModule: Module { public func definition() -> ModuleDefinition { Name("MyFirstExpoModule") Function("hello") { (name: String) in return "Hello \(name)!" } } }
class MyModule : Module() { override fun definition() = ModuleDefinition { Name("MyFirstExpoModule") Function("hello") { name: String -> return "Hello $name!" } } }
如需查看更多真实模块的示例,你可以参考 GitHub 上已经使用此 API 的 Expo 模块: