在React应用开发中,环境变量承担起了隔离配置与代码的重要职责。它允许我们把接口地址、功能开关、第三方标识等容易随部署环境变化的内容从业务代码里抽离出来,避免因为环境差异频繁修改源码并重新构建。React官方脚手架基于Webpack与dotenv实现了一套约定大于配置的方案,理解这套规则才能真正用好环境变量。

一、React环境变量的基本规则
React(以Create React App为例)对环境变量有严格的命名约束。只有以 REACT_APP_ 开头的变量才会被自动注入到应用的运行时环境中,并通过 process.env 对象暴露给JavaScript代码。如果你定义了一个名为 API_URL 的变量,它在构建时会被直接忽略,前端根本读取不到。
这种限制主要是出于安全与确定性的考虑。React应用最终打包为静态资源在浏览器中执行,所有出现在包里的变量对使用者都是明文可见的。限定前缀可以避免开发者误将系统级环境变量(如 NODE_ENV、PATH)打包进前端,也能让构建工具明确知道哪些值需要替换。下面的代码展示了在组件里读取环境变量的正确方式:
// 读取外部环境变量
const apiBase = process.env.REACT_APP_API_BASE;
function UserService() {
// 使用变量拼接请求地址
return fetch(apiBase + '/users')
.then(res => res.json());
}
// 当变量未定义时给出默认值
const timeout = process.env.REACT_APP_TIMEOUT || 5000;
从上面可以看到,我们在代码里通过 process.env.REACT_APP_API_BASE 获取配置,并为其设置了兜底值。这样做能保证即使运维漏配了变量,应用也不会直接崩溃,而是采用合理的默认行为。
二、env文件的加载优先级
dotenv在React项目里支持多种后缀的env文件,它们根据执行命令和文件名决定覆盖关系。常见文件包括 .env、.env.local、.env.development、.env.production 等。一般规则是:特定环境的文件会覆盖通用文件,而带 local 的文件优先级最高且通常不提交到版本库,用于本地临时覆盖。
举个例子,当运行 npm start 时(即开发模式),加载顺序大致为 .env → .env.development → .env.local → .env.development.local,后加载的同名变量会覆盖前面的。生产构建 npm run build 则对应 .env → .env.production → .env.local → .env.production.local。我们可以用一张表梳理常见文件用途:
| 文件名 | 适用场景 | 是否建议提交Git |
|---|---|---|
| .env | 所有环境通用基础配置 | 是 |
| .env.development | 仅开发环境生效 | 是 |
| .env.production | 仅生产构建生效 | 是 |
| .env.local | 本地覆盖,不进仓库 | 否 |
这种分层机制让团队可以把公开的配置放在 .env 中共享,而把个人本地的调试地址写在 .env.local 里,互不干扰。同时生产环境的真实地址由CI流水线在构建机写入 .env.production,避免敏感信息散落在开发者电脑上。
三、在代码中安全使用变量
虽然环境变量用起来方便,但必须时刻记住:React是纯前端框架,构建后所有 REACT_APP_ 变量都会被硬编码进JS包。因此绝不能把数据库密码、私钥或带权限的Token通过环境变量传给React应用。如果业务必须做鉴权,应该由后端服务持有秘钥,前端只拿到无状态的公开标识或短期访问令牌。
另外在TypeScript项目中,直接访问 process.env.REACT_APP_XXX 可能没有类型提示。我们可以在 src 目录下新建 react-app-env.d.ts 来声明变量类型,提升开发体验:
// react-app-env.d.ts
interface ProcessEnv {
readonly REACT_APP_API_BASE: string;
readonly REACT_APP_TIMEOUT?: string;
}
declare namespace NodeJS {
interface ProcessEnv extends ProcessEnv {}
}
加上声明后,编辑器会在拼写错误时给出警告,也能在编译阶段发现漏配关键变量的问题。配合CI里的类型检查,能大幅降低因环境变量缺失导致的线上故障。
四、动态注入与容器化部署
在Docker或Kubernetes环境里,我们往往不想把生产env文件打进镜像,而是希望容器启动时从外部注入。由于React在构建期就完成了变量替换,传统运行时注入环境变量对静态包无效。常见解法是构建一个空值占位、再用 sed 或启动脚本在 nginx 返回HTML前替换,或者采用运行时配置JS文件由 window 读取。
下面给出一个简单的构建后替换思路,在 public/config.js 中暴露配置,再由应用运行时读取:
<!-- public/config.js -->
<script>
// 该文件不被打包,可由容器挂载覆盖
window.__APP_CONFIG__ = {
apiBase: '<!--API_BASE-->'
};
</script>
在入口文件里我们可以先读取 window.__APP_CONFIG__ 再渲染React树,这样镜像保持不变,仅仅通过外部挂载不同 config.js 就能切换环境。虽然这偏离了原生dotenv用法,但在不可变基础设施场景中更加灵活,也规避了前端秘钥泄露风险。
五、常见误区与排查建议
新手常遇到“本地好用、打包后变量变成undefined”的情况,绝大多数原因要么是变量没加 REACT_APP_ 前缀,要么是修改env文件后没重启 dev server。dotenv只在进程启动阶段读取文件,运行中改动不会热更新。此外,在 index.html 里使用 %REACT_APP_TITLE% 这种占位符时,也必须保证前缀正确,否则会被原样保留成字符串。
推荐在应用启动早期打印一次配置摘要(注意脱敏),方便快速确认环境是否加载成功。例如:
// 启动调试用,生产可移除
console.log('当前环境配置', {
apiBase: process.env.REACT_APP_API_BASE,
env: process.env.NODE_ENV
});
通过规范命名、合理利用多文件优先级、区分构建期与运行时配置,以及严守前端不存秘钥的原则,React项目的外部环境变量就能既灵活又安全地支撑多环境交付。