React Native 环境变量方案选型:自定义、react-native-dotenv 与 react-native-config

React Native 环境变量方案选型:自定义、react-native-dotenv 与 react-native-config

React Native 项目经常需要区分开发、测试、预发布和正式环境。当项目还需要为不同客户生成独立安装包时,配置选型就会更加重要。本文将说清自定义构建注入、react-native-dotenvreact-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-dotenvreact-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 不是密钥保险箱,移动端构建产物中不应存放真正的服务端秘密。

最终选择不应只看"代码写起来短不短",而应该看配置需要被哪些层使用,以及构建流程是否还存在额外业务规则。

相关推荐
杉氧4 小时前
React 灵魂:深入剖析 Hooks 与副作用(Side Effects)陷阱
android·前端·react native
光影少年1 天前
react navite调试方案:Flipper、远程调试
前端·javascript·react native·react.js·前端框架
光影少年4 天前
react navite环境与工程化
前端·react native·react.js
真夜4 天前
RN热更新安全问题
前端·react native
杉氧4 天前
从 Modifier 到 Flexbox:React Native 布局与样式设计哲学
android·前端·react native
光影少年7 天前
react native打包优化、包体积瘦身、离线包/热更新方案
前端·react native·react.js
光影少年8 天前
react navite 内存泄漏:定时器、监听事件、订阅销毁
前端·javascript·react native·react.js·前端框架