用 expo-store-review 为 Expo 应用接入应用内评分(In-App Review)
【免费下载链接】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
expo-store-review是 Expo 官方提供的原生模块,允许用户在 App Store 与 Google Play Store 中直接为你的应用打分评价,而无需离开应用。本文以仓库中的 README 为骨架,结合 TypeScript 封装层 与 Android / iOS 原生实现,系统讲解该模块的安装步骤、全部 API 用法、平台差异与底层原理,读完即可在托管或裸工作流中落地一套完整的应用内评分方案。
模块概览与工作原理
从 package.json 的描述看,该模块的核心职责是“Provides access to native APIs for in-app reviews”,即暴露系统级的应用内评分能力:
- 在 iOS 上调用 StoreKit 的原生评分弹窗;
- 在 Android 上通过 Google Play In-App Review API 拉起系统评分流程;
- 在 Web 上不提供任何能力,相关调用会优雅降级为跳转应用商店链接。
模块按 Expo Modules API 组织:JS 层通过 src/ExpoStoreReview.native.ts 中的requireNativeModule('ExpoStoreReview')获取原生模块,原生侧在 android/.../StoreReviewModule.kt 与 ios/StoreReviewModule.swift 中注册同名模块,并在 expo-module.config.json 中声明仅支持apple与android两个平台。
安装指南
托管工作流(managed)
对于托管 Expo 项目,直接在项目根目录执行:
npx expo install expo-store-reviewnpx expo install会自动选取与当前 SDK 版本兼容的模块版本(当前仓库内模块版本为 57.0.1,见 package.json),无需手动配置原生工程。
裸工作流(bare)
裸 React Native 项目在安装前,必须确保已安装并配置好expo包本体,然后再添加模块依赖:
npx expo install expo-store-review平台配置差异
- Android:无需额外配置。从 AndroidManifest.xml 可以看到该模块的 manifest 是空的,Google Play 的 In-App Review 能力由模块运行时通过
ReviewManagerFactory动态获取,不要求声明权限或 Activity。 - iOS:安装 npm 包后执行一次 CocoaPods 安装。
npx pod-installiOS 侧依赖 ExpoStoreReview.podspec 声明的最小 iOS 版本为16.4(Swift 5.9),低于该版本的工程需要先升级部署目标。
API 详解(源码级)
模块对外暴露四个 API,全部实现在 src/StoreReview.ts 中,Web 平台使用 src/ExpoStoreReview.ts 的空实现。
isAvailableAsync()
判断当前平台是否具备原生评分能力:
import * as StoreReview from 'expo-store-review'; export async function isAvailableAsync(): Promise<boolean> { return StoreReview.isAvailableAsync?.() ?? false; }各平台返回值(与源码注释及原生实现一一对应):
| 平台 | 返回值 | 依据 |
|---|---|---|
| iOS | true(经 TestFlight 分发除外) | StoreReviewModule.swift 中isRunningFromTestFlight()判定 |
| Android | 设备已安装 Play Store 时为true | StoreReviewModule.kt 中isPlayStoreInstalled()检查 |
| Web | false | 空实现,无原生能力 |
requestReview()
理想情况下会弹出系统原生弹窗,让用户在不离开应用的情况下完成星级评分:
export async function requestReview(): Promise<void> { if (StoreReview?.requestReview) { return StoreReview.requestReview(); } // 原生能力不可用时,回退为打开商店链接 const url = storeUrl(); ... }两个值得注意的降级分支(StoreReview.ts):
- 原生模块缺失时:尝试通过
Linking.openURL打开商店页;若设备不支持打开该链接,会输出Can't open store url警告。 - 连商店 URL 都没有配置时:输出警告,提示在
app.json中填写android.playStoreUrl与ios.appStoreUrl字段。
在 Android 5.0 以下设备上,由于系统不支持 In-App Review API,模块同样会走链接跳转的降级路径。
storeUrl() 与 app.json 配置
storeUrl()是评分流程的兜底依赖,它通过expo-constants读取应用配置(StoreReview.ts):
export function storeUrl(): string | null { const expoConfig = Constants.expoConfig; if (Platform.OS === 'ios' && expoConfig?.ios) { return expoConfig.ios.appStoreUrl ?? null; } else if (Platform.OS === 'android' && expoConfig?.android) { return expoConfig.android.playStoreUrl ?? null; } return null; }对应在app.json中需要配置:
{ "expo": { "ios": { "bundleIdentifier": "com.example.myapp", "appStoreUrl": "https://apps.apple.com/app/id123456789" }, "android": { "package": "com.example.myapp", "playStoreUrl": "https://play.google.com/store/apps/details?id=com.example.myapp" } } }- 在 iOS 上返回
ios.appStoreUrl,在 Android 上返回android.playStoreUrl; - 在 Web 上恒返回
null。
hasAction()
评分流程“是否有事可做”的统一判定,推荐在调用requestReview()前先检查(StoreReview.ts):
export async function hasAction(): Promise<boolean> { return !!storeUrl() || (await isAvailableAsync()); }只要配置了商店 URL或原生评分能力可用,返回值即为true。官方推荐的用法:
import * as StoreReview from 'expo-store-review'; if (await StoreReview.hasAction()) { // 可以安全调用 requestReview() await StoreReview.requestReview(); }平台差异与底层实现
iOS:StoreKit 与 TestFlight 检测
StoreReviewModule.swift 的核心逻辑:
AsyncFunction("requestReview") { @MainActor () async throws in guard let currentScene = getForegroundActiveScene() else { throw MissingCurrentWindowSceneException() } if #available(iOS 16.0, *) { AppStore.requestReview(in: currentScene) } else { SKStoreReviewController.requestReview(in: currentScene) } }- iOS 16+使用新的
AppStore.requestReview(in:),旧版本回退到SKStoreReviewController; - 弹窗必须依附于前台活跃的
UIWindowScene,若应用处于后台或找不到合适场景,会抛出 StoreReviewExceptions.swift 中定义的MissingCurrentWindowSceneException,提示“无法确定展示评分弹窗的当前窗口场景”; - TestFlight 判定:通过检查 receipt 文件名是否为
sandboxReceipt且不存在内嵌的 provisioning profile 来识别沙箱/TestFlight 环境(模拟器环境直接放行返回false)。也就是说,TestFlight 分发下的评分入口会被isAvailableAsync()屏蔽。
Android:Google Play In-App Review
StoreReviewModule.kt 完整走一遍 Google Play Core 的 ReviewManager 流程:
AsyncFunction("requestReview") { promise: Promise -> requestReview(promise) }实现上依次执行ReviewManagerFactory.create(context)→manager.requestReviewFlow()获取ReviewInfo→manager.launchReviewFlow(activity, reviewInfo)拉起系统评分界面,并通过addOnCompleteListener区分成功与失败。异常统一映射为 StoreReviewExceptions.kt 中的RMTaskException/RMUnsuccessfulTaskException(CodedException子类)。
值得注意的工程细节:Google 官方限制每个用户每年最多弹出约 4 次系统评分框(配额由系统管理),因此模块本身不提供频率控制,业务侧应根据用户行为合理决定触发时机。
Web:明确不支持
Web 端通过 src/ExpoStoreReview.ts 的空对象实现,isAvailableAsync返回false、requestReview不存在,JS 层会自动降级为商店链接跳转,应用不会报错。
错误处理与最佳实践
结合上述源码行为,落地时建议遵循以下模式:
- 调用前先判断:用
hasAction()(或isAvailableAsync())做守卫,避免无意义的原生调用; - 补齐商店 URL:无论原生评分是否可用,都应在
app.json中配置ios.appStoreUrl/android.playStoreUrl,这是降级路径的唯一出口; - 选择恰当的触发时机:在用户完成关键操作、获得正向体验之后(如完成一次购买、通关关卡、使用一周)再请求评分,避免在启动或高频操作路径中打扰用户;
- 留意平台限制:TestFlight 包、无 Play Store 的设备、Web 端均无法弹出原生评分框,会走链接跳转或直接不执行,不要将其作为业务关键路径的依赖;
- 注意场景上下文(iOS):确保调用发生在应用处于前台活跃状态,否则会收到
MissingCurrentWindowSceneException。
小结
expo-store-review用极小的接入成本,让 Expo 应用同时获得 App Store 与 Google Play 两套系统级应用内评分能力,并在 Web、旧系统、TestFlight 等受限场景下自动降级为商店链接跳转。核心 API 仅四个(isAvailableAsync/requestReview/storeUrl/hasAction),配合app.json中的商店 URL 配置,即可快速集成;深入阅读 TypeScript 封装、Android 实现 与 iOS 实现,还能进一步掌握其平台检测与异常处理细节。
【免费下载链接】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),仅供参考