TypeScript 中 process.env 的 string | undefined 问题
1. 问题场景
在 Node.js 中,可以通过 process.env 读取环境变量。例如,在 db.ts 中使用环境变量创建 Sequelize 实例:
import "dotenv/config";
import { Sequelize } from "sequelize";
const db = new Sequelize({
database: process.env.DATABASE,
host: process.env.HOST,
username: process.env.USER_NAME,
password: process.env.PASSWORD,
dialect: "mysql",
});
export default db;
开启 TypeScript 严格检查后,这些配置项可能出现类型错误。原因是 TypeScript 将环境变量统一定义为:
string | undefined
即使 .env 文件中已经写了对应变量,TypeScript 在编译阶段也不会读取 .env 并证明变量一定存在。对 TypeScript 来说,下面两种情况都有可能发生:
process.env.DATABASE; // string:变量存在
process.env.DATABASE; // undefined:变量不存在
而 Sequelize 的数据库名称、用户名等配置通常要求确定的 string,所以 string | undefined 不能直接当作 string 使用。
2. 方式一:启动时校验环境变量(推荐)
可以编写一个函数,在程序启动时读取并检查必需的环境变量:
import "dotenv/config";
import { Sequelize } from "sequelize";
function getRequiredEnv(name: string): string {
const value = process.env[name];
if (value === undefined || value === "") {
throw new Error(`缺少必需的环境变量:${name}`);
}
return value;
}
const db = new Sequelize({
database: getRequiredEnv("DATABASE"),
host: getRequiredEnv("HOST"),
username: getRequiredEnv("USER_NAME"),
password: getRequiredEnv("PASSWORD"),
dialect: "mysql",
});
export default db;
为什么校验后是 string
在判断语句之前,value 的类型是:
string | undefined
如果值是 undefined 或空字符串,函数会抛出错误,后面的代码不会继续执行。因此,能够运行到 return value 时,TypeScript 可以确定 value 是一个非空字符串。
优点
- 同时保证编译阶段的类型安全和运行阶段的配置安全。
- 环境变量缺失时,能够立即给出明确错误。
- 不会把数据库连接错误误认为 Sequelize 或 MySQL 本身的问题。
适用场景
适用于数据库名、账号、密码等程序正常运行所必需的配置,是当前项目最推荐的处理方式。
3. 方式二:使用非空断言 !
如果确定运行环境中一定存在这些变量,可以使用非空断言:
import "dotenv/config";
import { Sequelize } from "sequelize";
const db = new Sequelize({
database: process.env.DATABASE!,
host: process.env.HOST!,
username: process.env.USER_NAME!,
password: process.env.PASSWORD!,
dialect: "mysql",
});
export default db;
! 是在告诉 TypeScript:
我可以保证这个值不是
null或undefined,请把它当作确定的字符串处理。
它只会改变 TypeScript 对值的判断,不会产生运行时校验代码,也不会自动补充缺失的环境变量。
例如,下面的代码可以通过类型检查:
const database: string = process.env.DATABASE!;
但是如果 DATABASE 实际不存在,运行时得到的仍然是 undefined。
优点
- 写法简短。
- 适合快速练习或已经由外部系统严格保证的配置。
风险
- 只是消除类型错误,没有解决环境变量可能缺失的问题。
- 配置错误可能推迟到数据库连接时才暴露,导致错误原因不够直观。
适用场景
适合简单示例,或者环境变量已经由部署平台、配置中心等外部机制可靠校验的场景。
4. 方式三:使用默认值 ??
如果某个环境变量确实允许缺省,可以使用空值合并运算符提供默认值:
import "dotenv/config";
import { Sequelize } from "sequelize";
const db = new Sequelize({
database: process.env.DATABASE ?? "nloop",
host: process.env.HOST ?? "localhost",
username: process.env.USER_NAME ?? "root",
password: process.env.PASSWORD ?? "",
dialect: "mysql",
});
export default db;
下面的表达式表示:如果 HOST 是 null 或 undefined,就使用 "localhost"。
process.env.HOST ?? "localhost"
无论环境变量是否存在,整个表达式最终都会得到一个字符串,因此可以满足 Sequelize 的配置类型要求。
?? 和 || 的区别
?? 只在左侧是 null 或 undefined 时使用默认值:
const value = process.env.PORT ?? "8080";
|| 则会把空字符串等所有假值都替换为默认值:
const value = process.env.PORT || "8080";
处理环境变量时,应根据空字符串是否有效来选择。通常 ?? 更准确地表达“变量没有定义时才使用默认值”。
优点
- 写法简单。
- 既能消除类型错误,也能在运行时提供实际值。
- 适合主机地址、端口、开发环境数据库名等有合理默认值的配置。
风险
- 不恰当的默认值可能掩盖
.env配置遗漏。 - 不建议为生产环境密码等敏感配置设置随意的默认值。
适用场景
适用于缺少配置时确实可以安全使用约定值的选项,例如本地开发环境中的 HOST=localhost。
5. 三种方式对比
| 方式 | 解决类型错误 | 运行时检查 | 变量缺失时的行为 | 推荐程度 |
|---|---|---|---|---|
| 启动时校验 | 是 | 是 | 立即抛出明确错误 | 推荐 |
非空断言 ! |
是 | 否 | 值仍可能是 undefined |
谨慎使用 |
默认值 ?? |
是 | 提供备用值 | 使用指定的默认值 | 按配置项使用 |
6. 当前项目的建议
对于当前 Sequelize 配置,可以按配置的性质分别处理:
DATABASE、USER_NAME等必需配置使用启动时校验。HOST如果允许连接本机数据库,可以提供localhost作为默认值。PASSWORD是否允许空字符串,应该由实际的 MySQL账号配置决定,不应只是为了消除 TypeScript 错误而设置默认值。- 非空断言适合演示 TypeScript 语法,但不应该被误认为运行时安全检查。
关键原则是:不要只考虑如何让红色类型提示消失,还要明确环境变量缺失时,程序应该立即报错还是采用安全的默认值。