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

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 不是密钥保险箱,移动端构建产物中不应存放真正的服务端秘密。

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

相关推荐
song5013 小时前
React Native for OpenHarmony 实战:三方库 react-native-css-transformer 的鸿蒙化适配指南
css·人工智能·深度学习·react native·transformer
enjoywindstorm2 天前
移动端原生框架对比评测
react native
传奇开心果编程3 天前
【声明式UI开发实用技术学与练】第8课 状态提升与下放
学习·flutter·react native·ui·swiftui·composer
传奇开心果编程4 天前
【现代声明式UI学与练】第4课 列表渲染与 key——如何高效渲染列表、key 的作用、列表重排时的状态保持
学习·flutter·react native·ui·swiftui·android jetpack
传奇开心果编程4 天前
【现代声明式UI学与练】第7课 组件通信——父传子、子传父、跨层级通信、状态提升与 Context
学习·flutter·react native·ui·swiftui·android jetpack
500845 天前
React Native for OpenHarmony 实战:三方库 react-native-url-polyfill 的鸿蒙化适配指南
javascript·react native·react.js·性能优化·electron·harmonyos
传奇开心果编程5 天前
【现代声明式UI学与练】第9课 性能优化——渲染优化、列表优化、内存优化、启动优化
学习·flutter·react native·ui·性能优化·swiftui·android jetpack
500845 天前
React Native for OpenHarmony 实战:三方库 react-native-volume-control 的鸿蒙化适配指南
javascript·react native·react.js·electron·harmonyos
5008410 天前
React Native for OpenHarmony 实战:三方库 react-native-crypto-js 的鸿蒙化适配指南
javascript·react native·性能优化·electron·harmonyos
5008410 天前
React Native for OpenHarmony 实战:三方库 react-native-device-name 的鸿蒙化适配指南
深度学习·react native·react.js·机器学习·harmonyos