React Native 环境变量方案选型:自定义、react-native-dotenv 与 react-native-config
React Native 项目经常需要区分开发、测试、预发布和正式环境。当项目还需要为不同客户生成独立安装包时,配置选型就会更加重要。本文将说清自定义构建注入、
react-native-dotenv和react-native-config各自解决什么问题。
一、先说结论:它们不是完全对等的三种方案
这三者都可以提供"不同构建使用不同配置"的能力,但它们工作的层级不同:
| 方案 | 主要工作层 | 适合解决的问题 |
|---|---|---|
react-native-dotenv |
JavaScript / Babel | 将 .env 变量写入 JS Bundle |
react-native-config |
Android、iOS 原生构建 + JavaScript | 让 JS 和原生代码共享构建配置 |
| 自定义脚本 | 构建流程 | 校验、命名、分目录、多品牌打包等特殊规则 |
最实用的判断方式是:
text
安装后配置是否需要立即变化?
├─ 是 → 后端配置接口 / MMKV / Remote Config
└─ 否
├─ 只有 JavaScript 使用 → react-native-dotenv
├─ Android/iOS 原生代码也要使用 → react-native-config
└─ 还有特殊打包规则 → 在上述方案外增加小型自定义脚本
二、react-native-dotenv:适合 JavaScript 专用配置
react-native-dotenv 是一个 Babel 插件。它在打包时读取 .env 文件,然后将代码中引用的变量替换成字面量。
1. 基本用法
.env 文件:
dotenv
API_URL=https://test.example.com
ENABLE_DEBUG=true
JavaScript 中使用:
javascript
import { API_URL, ENABLE_DEBUG } from '@env';
fetch(`${API_URL}/api/users`);
打包后的效果类似于:
javascript
const API_URL = 'https://test.example.com';
const ENABLE_DEBUG = 'true';
注意:.env 的值默认都是字符串。布尔值需要显式转换:
javascript
const allowDebug = ENABLE_DEBUG === 'true';
2. 多环境配置
可以为每个环境创建独立文件:
text
.env
.env.test
.env.staging
.env.environmentA
.env.environmentB
通过 APP_ENV 选择环境:
bash
APP_ENV=environmentA yarn android
在 package.json 中可以封装成固定命令:
json
{
"scripts": {
"android:test": "APP_ENV=test react-native run-android",
"build:android:a": "APP_ENV=environmentA node scripts/build-android.js environmentA",
"build:android:b": "APP_ENV=environmentB node scripts/build-android.js environmentB"
}
}
3. 适用场景
- API 服务器地址。
- WebSocket 地址。
- JavaScript 功能开关。
- Sentry 环境名称等公开配置。
- 不同部署环境的前端行为区分。
4. 不适用的情况
- Android Manifest 需要读取该变量。
- Java、Kotlin、Objective-C 或 Swift 代码需要使用该变量。
- 需要根据环境修改
applicationId、Bundle Identifier 或原生 SDK Key。
react-native-dotenv 官方文档:dotenvx/react-native-dotenv
三、react-native-config:适合 JS 与原生层共享配置
react-native-config 同样使用 .env 文件,但它不只服务于 JavaScript。它会将配置接入 Android 和 iOS 的原生构建流程。
1. JavaScript 中使用
javascript
import Config from 'react-native-config';
console.log(Config.API_URL);
2. Android 原生层使用
Java/Kotlin 可以通过 BuildConfig 读取:
java
String apiUrl = BuildConfig.API_URL;
Gradle 中也可以使用:
gradle
defaultConfig {
applicationId project.env.get("APP_ID")
}
这使它可以配置:
- Android
applicationId。 - App 显示名称。
- Android Manifest 中的
meta-data。 - 推送、地图等原生 SDK 的 Key 或 Channel。
- Android Product Flavor 和 iOS Scheme。
3. 适用场景
假设一个项目需要生成两个独立品牌的 App:
text
Environment A
├─ App 名称:Warehouse A
├─ applicationId:com.example.warehouse.a
├─ 推送渠道:environment_a
└─ API:https://a.example.com
Environment B
├─ App 名称:Warehouse B
├─ applicationId:com.example.warehouse.b
├─ 推送渠道:environment_b
└─ API:https://b.example.com
这种"原生层也需要根据环境变化"的场景,更适合 react-native-config。
4. 引入成本
react-native-config 是原生模块,通常需要:
- 安装与当前 React Native 版本兼容的库版本。
- 调整 Android Gradle 配置。
- iOS 执行 Pod 安装并配置 Scheme。
- 维护不同 Flavor 与
.env的映射关系。
如果变量只在 JavaScript 中使用,引入这些原生配置可能得不偿失。
react-native-config 官方文档:react-native-config/react-native-config
四、什么时候需要自定义脚本
自定义脚本不应只为了"读取一个 .env"而存在。它更适合处理环境变量库不负责的构建规则。
1. 构建前校验
例如,某个正式环境还没有配置 API 地址时,必须禁止生成安装包:
javascript
if (!process.env.API_URL) {
throw new Error('正式环境 API_URL 未配置,已终止打包');
}
2. APK 命名与归档
text
android/app/build/outputs/apk/
├─ 测试包/
│ └─ Warehouse-Test-1.2.3.apk
├─ Environment-A/
│ └─ Warehouse-A-1.2.3.apk
└─ Environment-B/
└─ Warehouse-B-1.2.3.apk
3. 平台自动化
- macOS 构建完成后自动打开 Finder。
- Windows 构建完成后打开资源管理器。
- CI 将安装包上传到制品库。
- 为文件名加入版本号、Git Commit 或构建时间。
4. 特殊的多品牌规则
不同品牌可能不只服务器地址不同,还有不同的菜单、Logo、功能开关和强制升级规则。这类逻辑通常仍需要自定义的构建封装。
五、环境变量库不能替代业务逻辑
很多项目引入 .env 后,容易误以为所有环境差异都应放进 .env。实际上,环境变量只负责"提供值",不负责实现业务行为。
假设测试包允许用户切换服务器,正式包则必须锁定服务器:
javascript
const allowHostSwitch = ALLOW_HOST_SWITCH === 'true';
if (allowHostSwitch) {
// 显示服务器选择界面
} else {
// 使用当前构建中的固定地址
}
这段逻辑必须保留在业务代码中。无论使用 react-native-dotenv 还是 react-native-config,它们都不会自动隐藏页面或阻止地址切换。
六、什么才是"实时配置"
react-native-dotenv 和 react-native-config 都不是实时配置。
text
修改 .env
↓
重新打包
↓
安装新 APK / IPA
↓
新配置生效
如果要求 App 安装后仍能修改配置,应该使用:
- 后端配置接口。
- Firebase Remote Config 等远程配置服务。
- MMKV、AsyncStorage 等本地存储。
- 登录后由租户或用户配置覆盖默认值。
这些属于运行时配置,和构建时环境变量是两套不同的机制。
七、安全误区:.env 不是密钥保险箱
无论使用哪种方案,最终配置都会进入 JavaScript Bundle、Android BuildConfig 或 iOS 构建产物。获取到安装包的人有可能提取它们。
可以放入 .env 的内容:
- 公开 API 地址。
- 环境名称。
- 非敏感功能开关。
- 前端本来就需要使用的公开标识。
不应放入 .env 的内容:
- 数据库密码。
- 服务端私钥。
- 签名证书密码。
- 拥有高权限的长期 Token。
真正的秘密必须保留在服务端或 CI/CD 的密钥管理系统中。
八、一个更实用的分层方案
在一个只需要为不同环境固定 API 地址,同时对 APK 进行归档的 React Native 项目中,可以采用以下分层:
text
react-native-dotenv
负责:API 地址、功能开关、环境标识
业务代码
负责:是否展示服务器选择、如何处理旧缓存
Gradle
负责:APK 文件名、签名、版本号
小型 build-android.js
负责:构建校验、分目录归档、打开产物目录
这样每一层只负责自己擅长的事情,既不需要为简单的 JS 配置引入过重的原生集成,也不会把所有打包逻辑堆在 Babel 中。
九、常见问题
1. 修改 .env 后没有生效
通常是 Metro 或 Babel 缓存导致的,可以重启服务:
bash
yarn start --reset-cache
正式打包流程还应该将当前环境名称或 .env 文件声明为 Gradle 任务输入,避免 Gradle 误用上一个环境的 JS Bundle。
2. process.env 在手机上存在吗
React Native 中不能把 Node.js 的 process.env 当作通用运行时环境变量。能够使用,是因为 Babel 在构建时将指定引用替换了。
3. 配置为什么都是字符串
.env 文件本质上是键值对文本。数字、布尔值和 JSON 都需要在代码中转换和校验:
javascript
const requestTimeout = Number(REQUEST_TIMEOUT);
const allowHostSwitch = ALLOW_HOST_SWITCH === 'true';
const hostMenu = JSON.parse(HOST_MENU_JSON);
4. 应该把 .env 提交到 Git 吗
对于不敏感且需要稳定构建的环境配置,可以根据团队规范决定是否提交。更通用的做法是提交 .env.example 或 .env.template,真实值由本地或 CI 提供。
十、总结
- 只有 JavaScript 需要区分环境时,优先考虑
react-native-dotenv。 - 原生代码、Manifest、包名或原生 SDK 也需要读取环境配置时,考虑
react-native-config。 - 需要构建校验、APK 命名、分目录和 CI 归档时,使用小型自定义脚本。
- 需要安装后动态变化时,使用运行时或远程配置,不要使用构建时
.env。 .env不是密钥保险箱,移动端构建产物中不应存放真正的服务端秘密。
最终选择不应只看"代码写起来短不短",而应该看配置需要被哪些层使用,以及构建流程是否还存在额外业务规则。