Expo Widgets 深度指南:用 Expo UI 组件构建 iOS 主屏小组件与 Live Activities

发布时间:2026/9/10 6:26:20
Expo Widgets 深度指南:用 Expo UI 组件构建 iOS 主屏小组件与 Live Activities Expo Widgets 深度指南用 Expo UI 组件构建 iOS 主屏小组件与 Live Activities【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expoexpo-widgets是 Expo 官方提供的模块让你直接使用 Expo UI 的 React 组件而非系统原生 API来编写 iOS 主屏小组件WidgetKit与 Live ActivitiesActivityKit并通过统一的 JavaScript API 管理时间线timeline、刷新与推送更新。本篇基于 Expo 仓库中 packages/expo-widgets/README.md 的说明展开结合模块的 JavaScript 实现、配置插件config plugin与原生端源码梳理其安装方式、核心 API、事件模型与平台差异帮助你在 managed 与 bare React Native 项目中正确接入并理解其底层机制。一、模块定位用 Expo UI 组件写小组件README 开篇即点明该模块的核心价值Build iOS home screen widgets and Live Activities using Expo UI components——小组件的 UI 层完全复用 Expo UI 组件体系而刷新、时间线、推送等生命周期能力由原生端iOS 的 WidgetKit/ActivityKitAndroid 的 AppWidgetProvider承载。从仓库结构可以看到模块由四部分构成JavaScript API 层src/Widgets.ts、src/Widgets.types.ts 等导出createWidget、createLiveActivity等函数原生实现层ios/ 下的 Swift 代码如 WidgetsModule.swift、TimelineProvider.swift、WidgetLiveActivity.swift以及 android/src/main/java/expo/modules/widgets/ 下的 Kotlin 代码如 ExpoWidgetsAppWidgetProvider.kt、WidgetsUpdater.kt配置插件层plugin/src/withWidgets.ts 等 config plugin负责在 prebuild 时把 Widget Target、App Group、推送能力等注入到原生工程独立 Bundle 层bundle/ 目录用于把小组件布局编译成可独立运行的 JS bundle由原生端的独立 Hermes Runtime 执行参见 android/src/main/cpp/WidgetsHermesRuntime.cpp 与 ios/Widgets/WidgetsJSRuntime.swift。当前仓库中该包版本为57.0.7见 package.json依赖expo/ui与expo/plistpeer dependencies 为expo、react、react-native。二、安装方式README 将安装分为两类项目Managed Expo 项目遵循官方 API 文档中针对最新稳定版的安装说明README 中给出的 docs.expo.dev 链接属外部文档此处不重复输出。Bare React Native 项目必须先安装并配置好expo包再执行npx expo install expo-widgets安装后若使用 Expo 的 prebuild 流程还需要在app.json中通过 config plugin 声明 widget。README 虽未给出完整配置示例但可以从插件源码 plugin/src/withWidgets.ts 得到完整、可复制的配置参数{ plugins: [ [ expo-widgets, { bundleIdentifier: 主App的BundleID.ExpoWidgetsTarget, groupIdentifier: group.主App的BundleID, enablePushNotifications: true, frequentUpdates: false, enableAndroid: false, widgets: [] } ] ] }各参数的语义直接来自 withWidgets.ts 中的类型定义ExpoWidgetsConfigPluginProps参数默认值说明bundleIdentifier主App BundleID.ExpoWidgetsTargetWidget 独立 Target 的 bundle identifiergroupIdentifiergroup.主App BundleID主 App 与 Widget 之间通信所用的 App Group 标识enablePushNotificationsfalse是否为 Widget 启用推送通知Live Activity 远程更新依赖 APNs 推送 tokenfrequentUpdatesfalse是否启用更频繁的时间线更新enableAndroidfalse是否启用 Android 配置插件源码注释指出该选项未来会移除、Android widget 将默认启用widgets[]WidgetConfig[]声明具体 widget 配置插件执行逻辑也很清晰先按enableAndroid决定是否运行 Android 插件链android/withAndroidWidgets.ts再统一运行 iOS 插件链ios/withIosWidgets.ts。iOS 插件链内部还拆分为多个步骤文件如 withAppGroupEntitlements.ts写入 App Group 权限、withPushNotifications.ts配置推送、withPodsLinking.tsCocoaPods 链接与 Xcode 工程操作工具xcode/withTargetXcodeProject.ts 等最终由 app.plugin.js 以expo-widgets插件名对外暴露插件工厂见 plugin/src/index.ts使用createRunOncePlugin保证同一插件只执行一次。三、JavaScript APIWidget、LiveActivity 与事件expo-widgets的所有运行时导出集中在 src/index.ts三类值导出Widgets.ts中的函数与类加上 Widgets.types.ts 中的类型定义。3.1 创建与更新 Widget核心入口是createWidgetWidgets.tsexport function createWidget( name: string, // 必须与 app config 中 widget 配置的 name 字段一致 widget: (props, context) React.JSX.Element, // 带 widget 指令的布局组件 initialProps?: object ): WidgetPropsType, ConfigurationTypeWidget类Widgets.ts提供了时间线管理能力reload()强制刷新 widget触发其内容与时间线重载updateTimeline(entries)按[{ date, props }]批量排程时间线条目。注意平台差异源码中明确if (Platform.OS android) return;——即该方法仅在 iOS 生效updateSnapshot(props)立即替换内容而不排程时间线。此处也体现了双端差异Android 走nativeWidgetObject.updateSnapshotiOS 则退化为一条timestamp: Date.now()的时间线条目getTimeline()异步返回当前包含过去与未来条目在内的完整时间线setConfigurationParameterEnum(parameterName, options)为“动态枚举配置参数”App Intents 驱动的可选配置在运行时替换选项app config 中的值仍作为回退。3.2 Live ActivitiesLiveActivityFactory 与 LiveActivityLive Activity 通过createLiveActivity(name, layout)创建工厂Widgets.ts同样要求name与 app config 中 widget 配置的name字段匹配。工厂类LiveActivityFactoryWidgets.ts提供start(props, url?, staleDate?)启动一个新的 Live Activityurl用于深链staleDate让系统在内容长时间未刷新时降低其视觉强调getInstances()获取该类型当前所有活动实例。每个LiveActivity实例Widgets.ts提供getId()ActivityKit 的稳定标识符update(props, staleDate?)更新内容UI 立即反映end(dismissalPolicy?, props?, contentDate?)结束活动。dismissalPolicy支持default、immediate或after(date)——after()辅助函数Widgets.ts构造一个在指定时间点4 小时窗口内从锁屏移除的策略对象源码中end()会将其展开为after 时间戳传给原生端getPushToken()返回用于经 APNs 推送内容更新的推送 tokenaddPushTokenListener(listener)监听 token 更新事件底层事件名onExpoWidgetsTokenReceived。此外还有两个全局监听函数addUserInteractionListener监听按钮点击等交互事件事件名onExpoWidgetsUserInteraction与addPushToStartTokenListener监听可用于远程启动 Live Activity 的 push-to-start token事件名onExpoWidgetsPushToStartTokenReceived见 Widgets.ts。最后一个值得注意的导出是widgetsDirectory——一个主 App 与 widget 都可访问的共享目录常用于存放共享图片。3.3 环境对象与类型组件渲染时收到的第二个参数WidgetEnvironmentWidgets.types.ts暴露了系统环境信息对锁屏/主屏适配非常有用widgetFamily组件族尺寸systemSmall2x2、systemMedium4x2、systemLarge4x4、systemExtraLarge仅 iPad6x4以及锁屏专用accessoryCircular、accessoryRectangular、accessoryInlinecolorScheme、isLuminanceReducediOS 16提示你降低亮度渲染、widgetRenderingModefullColor主屏 /accentediOS 18 着色 widget /vibrant锁屏、showsWidgetLabelwidgetContentMarginsiOS 17 的内容边距与levelOfDetailiOS 26simplified/default系统根据用户距离等推荐简化视图configurationwidget 配置参数由 App Intents 驱动iOS 17。Live Activity 侧有对应的LiveActivityEnvironment并新增isActivityFullscreeniOS 16.1等字段ActivityFamilysmall/medium说明同一 Activity 在不同设备上可能渲染为不同族尺寸iOS 18。四、平台差异与 Web 回退从源码结构看该模块的主战场是 iOSiOS 端完整实现了 WidgetKit/ActivityKit 桥接ios/ 目录包含 20 余个 Swift 文件如 TimelineEntry.swift、LiveActivityFactory.swift、WidgetsStorage.swiftAndroid 端提供ExpoWidgetsAppWidgetProvider、WidgetsUpdater、WidgetsJSRuntime等实现android/src/main/java/expo/modules/widgets/但需要enableAndroid: true显式开启配置插件且 JS 侧的部分 API 做了平台区分如updateTimeline在 Android 上直接返回Web/非原生环境下src/ExpoWidgets.native.ts 的加载机制会回退到 stub 实现见 ExpoWidgets.ts 中的WidgetStub、LiveActivityStub等空实现类保证代码在 Web 平台可以安全 import 而不报错。这一分层设计native 模块 stub 回退是 Expo 模块的标准模式意味着你的小组件代码可以无差别地在多平台构建流程中参与编译。五、原生端如何渲染你的 JSX一个常被问到的底层问题是小组件进程独立于主 AppJS 代码如何执行从仓库结构可以推断出其工作方式插件与构建脚本把 widget 布局编译为独立 bundle——bundle/ 目录含 decorator.ts、index.ts 及一系列 stub 文件配合 layout-registry.metro.config.js 完成布局注册原生端为 widget 启动独立的 JS RuntimeiOS 见 ios/Widgets/WidgetsJSRuntime.swiftAndroid 见 android/src/main/cpp/WidgetsHermesRuntime.cpp 及其 JNI 封装 jni/WidgetsHermesRuntime.kt主 App 与 widget 进程之间通过 App Group 共享存储交换时间线数据iOS 侧有 WidgetsStorage.swiftAndroid 侧有 WidgetsStorage.kt这也解释了为何插件默认会写入group.主App BundleID的 App Group 权限。六、贡献与延伸阅读README 末尾指出该模块欢迎贡献规范遵循 Expo 仓库的贡献指南对应仓库根目录的 CONTRIBUTING.md。模块自带 Jest 测试jest.config.jsbundle 层测试包括 decorator.test.ts 与 jsx-runtime.test.ts配置插件测试见 plugin/src/tests/。完整的 API 级文档请以官方 SDK 文档latest stable 与 main 分支两个版本为准README 中已给出对应入口本文则以仓库中 packages/expo-widgets/ 的实际源码为准绳覆盖其安装、配置、JS API 与平台机制适合作为在 Expo 项目中落地小组件与 Live Activities 的参考。【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考