"
并排安装,是开发体验的真正升级——不再为调试而反复卸载生产应用。
你刚把应用发布到生产环境,用户正在安装它。但很快,第一个 bug 报告就来了。你打开手机上的生产构建,确实,问题就在那里。你起草了修复方案,安装开发构建,开始调试——然后发现,手机上同一时间只能安装一个版本。于是你卸载生产应用,装上 dev build,调试完再装回生产应用。一周三次,一定有更好的办法。
Expo 的 App Variants通过给每个构建赋予自己的标识符来解决这个问题。这样不同变体(例如 dev、preview 和 production)可以并排安装在同一设备上,每个都被视为独立的应用。本文将完整讲解配置流程与实战技巧。
📌 本文看点
01
给每个构建独立标识,实现并排安装
02
从动态配置到 EAS 构建全流程
03
QR 码、图标、更新对齐实战
THE PROBLEM
为什么需要 App 变体
手机上同一时间只能安装一个版本的应用。听起来合理,直到你一周内第三次为了调试 dev build 而卸载生产应用,然后再重新安装,再卸载。App Variants 通过给每个构建赋予自己的标识符来解决这个问题——不同变体可以并排安装在同一设备上,每个都被视为独立的应用。
「这是一个真正的开发体验升级,也是我在实际项目中第一个配置的东西。」
ANATOMY
App 变体的两个维度
每个构建有两个独立的设置:身份(Identity)和环境(Environment)。理解这两个维度,是掌握 App Variants 的前提。
身份(Identity)
指 iOS 上的 bundle identifier 或 Android 上的 package name,例如 com.myapp.app。标识被固化在原生构建中,它决定什么代表一个唯一应用,以及新安装是否替换旧安装。设备每个标识只保留一个应用——如果开发和生产构建共享一个标识,安装任一个都会替换另一个。
环境(Environment)
指当你的 app config 被评估时加载的一组变量。EAS 有三个内置环境:development、preview和production。环境决定了应用运行后的行为方式,诸如 API URL 或 analytics key 就存在于环境中。
💡 重要说明:expo start 时做的任何操作都不会改变标识。本指南适用于你在本机或通过 EAS Build 创建的自定义构建,不适用于 Expo Go。
CONFIG
配置应用支持多变体
你的项目可能目前有一个静态的 app.json。静态文件适合稳定值,但变体并排安装需要各自的标识符,而标识符在 app config 中设置,静态文件只能持有一个。所以你需要动态配置(Dynamic Config),推荐使用 app.config.ts。
两个配置文件的协作
当两个文件都存在时,Expo 先读取 app.json,将其作为 { config }传递给动态配置,然后使用动态配置返回的结果。app.json 是基础层,app.config.ts 是上层的薄覆盖层。
基于 APP_VARIANT 切换
创建 App Variant 需要告诉 config 正在构建哪一个。为此引入 APP_VARIANT——一个配置读取的普通环境变量。使用 switch语句将每个变体放在自己的行上,共享的 ID 部分放在 APP_ID_PREFIX中,每个 case 只设置后缀。
调试配置时可以用以下命令查看 Expo 将使用的已解析配置:
npx expo config # 读取已解析配置
npx expo config --json # 机器可读输出
npx expo config --json | jq .name # 提取单个字段
为什么保留两个文件
可以只用 app.config.ts,很多应用确实这样做。但问题是:Expo 工具只会写入静态 app.json。某些服务更进一步需要静态 app.json——例如 Expo Launch 没有该文件就无法运行,因为它需要将标识字段写入配置,而动态配置无法接受这些。
默认为开发环境
让缺失的 APP_VARIANT落入 development 不是必需的,但作者更倾向于只在明确要求时才使用生产标识。本地运行的命令不要求选择变体,所以默认值就是你得到的——本地几乎每次都是 dev build。落入 development 让你保持在开发后端;落入 production 会将你发送到真实后端,可能悄悄成功并用测试数据污染生产环境。
EAS BUILD
在 EAS 上构建每个变体
配置现在可以对 APP_VARIANT做出反应,EAS Build 需要为每次构建设置该变量。最简单的方式是在 eas.json中为每个 profile 设置 env块。运行 eas build --profile development时,EAS 会在评估配置前设置 APP_VARIANT=development,dev build 安装在生产旁边而不会冲突。
变量存放位置
eas.json中每个 profile 的 env块足以开始构建变体,但有限制:env 块仅适用于 eas build,eas update 无法看到它,本地命令也需要从 shell 读取。更好的方案是使用 EAS 环境变量:
# 创建变量
eas env:create --name APP_VARIANT \
--value development \
--environment development \
--visibility plaintext
# 本地拉取环境变量
eas env:pull --environment development
这样 expo start和 expo run从 .env.local读取 APP_VARIANT,构建从 EAS 上的同一环境读取。只需在一个地方更改值。
在本机构建变体
本地使用 expo run构建一旦变量在 .env.local中就不需要额外设置。切换变体时需用 prebuild --clean重新生成原生项目:
APP_VARIANT= npx expo prebuild --clean
npx expo run:[platform]
本地构建 Release 配置(嵌入 JS 和配置,无需 dev server):
# iOS
npx expo run:ios --configuration Release
# Android
npx expo run:android --variant release
!踩坑提示 🕳
CNG(Continuous Native Generation)按需从 app config 生成原生项目,而不是将它们提交到源代码控制。切换回开发环境时,务必运行 APP_VARIANT=development npx expo prebuild --clean重新生成。
RUNTIME
环境如何到达运行中的应用
身份在构建时固定。开发时需注意:如果已用 npx expo prebuild生成了本地原生项目,expo start从磁盘上的原生项目获取其启动 scheme,APP_VARIANT不影响打开哪个已安装的应用。
运行时通过 expo-constants读取的配置取决于构建类型:
⚠️ 关键注意点:以 preview 启动服务器后打开 dev build,它会无报错地使用 preview 环境的值运行——dev 菜单甚至会显示「MyApp (Preview)」在一个标识为 dev 的构建上。修复方法是匹配服务器变体与构建变体。
EXPO_PUBLIC_ 变量与 extra 访问
EXPO_PUBLIC_变量在 bundle time 内联到 JS bundle 中。没有该前缀的变量(包括 APP_VARIANT)永远不会直接到达 JS,需通过配置的 extra字段暴露:
import Constants from "expo-constants";
variant: {Constants.expoConfig?.extra?.variant}
APP_VARIANT: {String(process.env.APP_VARIANT)}
EXPO_PUBLIC_APP_VARIANT: {String(process.env.EXPO_PUBLIC_APP_VARIANT)}
APP_VARIANT渲染为 undefined(缺少 EXPO_PUBLIC_前缀),而 EXPO_PUBLIC_APP_VARIANT显示其值。Preview 或生产安装继续显示编译时的变体,只有 dev build 从服务器获取新值。
PRACTICAL TIPS
QR 码、服务注册与图标
让 QR 码打开你的 dev build
安装多个变体时,Expo CLI 的 QR 码可能打开错误的应用。原因是 expo-dev-client默认添加一个从应用 slug 生成的 scheme exp+,slug 在变体间相同,所以每个变体注册相同的 scheme 并响应相同的链接。OS 打开哪个应用不可靠。修复方法是让 dev build 成为唯一响应该 scheme 的应用:
plugins: [
[
"expo-dev-client",
{
addGeneratedScheme: process.env.APP_VARIANT === "development",
},
],
],
每次开发会话前重新生成 development 原生项目:APP_VARIANT=development npx expo prebuild --clean。如果变量在 EAS 上,先运行 eas env:pull --environment development。
向服务注册每个变体
APP_VARIANT可以使配置返回不同的 bundle identifier 或 package name,但无法创建这些标识符所需的外部注册。如果服务在设置时要求你的 package name 或 bundle identifier,就需要为每个变体单独设置。对于记录或发送生产数据的任何内容(如分析、崩溃报告、推送通知),单独注册通常是值得的,这样 dev build 就不会污染生产数据。
给每个变体自己的图标
安装两个或三个并排时,不同的图标是避免点错的最快方式。实现方式:使用动态配置中的一个额外 helper,变体图像可以简单到相同图标配不同背景色。生产环境返回 undefined,让 app.json中的图标原样通过。注意 ios.icon和 android.adaptiveIcon优先于顶层 icon,所以也要覆盖它们。
UPDATES
在更新中保持变体对齐
EAS Update 是构建时间之外保持对齐的又一个地方。两个关键概念:Channel(通道)决定更新到达哪个变体,来自构建 profile;Environment(环境)决定更新携带的值,通过 --environment设置。
保持名称一致,整条链匹配:preview变体由 previewprofile 构建,加载 preview环境,在 preview通道接收更新:
eas update --channel preview --environment preview
「通道路由更新,环境决定行为,保持它们对齐是你的责任。将通道指向错误的环境,preview 变体会愉快地接受携带开发环境值的更新,没有任何报错。」
THE END
总结
这看起来可能很多,但入门真的很简单。你添加一个读取一个变量的动态配置、每个环境一个构建 profile、以及匹配的通道,你关心的每个构建就都存在一部手机上,每个都诚实地表明自己的身份。它很少需要更新,作者现在将相同的配置复制到新项目中只需少量修改。
「当生产 bug 在你开发功能中途出现时,你切换到生产环境,复现它,然后切回你之前的状态——没有卸载任何东西,没有丢失任何东西。」
App Variants 是你们都应得的开发体验的重要组成部分。
我是李青仪,热衷于分享 React Native 与 Expo 开发干货。
如果你觉得今天这篇有收获,欢迎点赞、在看、转发三连,我们下篇见。
夜雨聆风