在TypeScript开发中,有两个"空值"概念让无数开发者困惑:undefined和null。虽然在JavaScript中它们都可表示"无值",但在TypeScript严格模式下,它们的区别至关重要。本文将深入剖析这对双胞胎的本质差异,并提供实战中明确的决策指南。
一、核心区别:不只是两个不同的值
1.1 类型系统层面的根本差异
在开启strictNullChecks的严格模式下,undefined和null是完全独立的类型:
TypeScript
// 严格模式下,这两行都会编译失败
let value: string;
value = undefined; // ❌ Error: Type 'undefined' is not assignable to type 'string'
value = null; // ❌ Error: Type 'null' is not assignable to type 'string'
// 必须显式声明联合类型
let val1: string | undefined;
let val2: string | null;
val1 = undefined; // ✅ 通过
val2 = null; // ✅ 通过
1.2 语义的本质区别
| 类型 | 语义含义 | 典型场景 |
|---|---|---|
| undefined | 未定义 - 变量存在但尚未赋值 | 可选参数、未初始化的变量、缺失的属性 |
| null | 空值 - 明确赋值为"无"或"空" | 主动表示对象为空、API响应的null值 |
TypeScript
class User {
// undefined:属性存在但值为空
nickname: string | undefined = undefined;
// null:明确标记"没有推荐人"
referredBy: string | null = null;
}
二、必须使用undefined的场景
2.1 TypeScript语法层面的强制要求
TypeScript
// ✅ 正确:可选参数本质就是undefined
function fetchData(url: string, options?: RequestOptions) {
console.log(options); // 未传值时为undefined,绝不可能是null
}
// 错误示范:试图给可选参数传null
fetchData('/api', null); // ❌ Error: Argument of type 'null' is not assignable
2.2 解构默认值的触发机制
TypeScript
interface Config {
timeout?: number;
}
// 只有当值为undefined时才会触发默认值
function createClient(config: Config) {
const { timeout = 5000 } = config;
return timeout;
}
createClient({}); // 5000 ✅
createClient({ timeout: 3000 }); // 3000 ✅
createClient({ timeout: undefined }); // 5000 ✅
createClient({ timeout: null }); // null ❌ 不会触发默认!
2.3 访问不存在的属性
TypeScript
const user = { name: 'Alice' };
user.age; // 自动推断为number | undefined
user['height']; // 自动推断为any | undefined
// 这些场景永远不可能得到null
三、必须使用null的场景
3.1 与JavaScript原生API交互
TypeScript
// DOM API明确返回null
const element = document.querySelector('.non-existent');
if (element === null) {
// 必须用null判断,element绝不会是undefined
}
// 正则表达式匹配
const match = 'hello'.match(/world/);
console.log(match); // null,不是undefined
3.2 区分"未设置"与"设置为空"
这是null最强大的用途------表达两种业务状态:
TypeScript
interface Article {
// undefined: 尚未发布
publishedAt?: Date;
// null: 已明确决定不发布
cancelledAt: Date | null;
}
function getArticleStatus(article: Article): string {
if (article.cancelledAt === null) {
return 'active'; // 明确未取消
}
if (article.cancelledAt) {
return 'cancelled'; // 已取消
}
if (article.publishedAt === undefined) {
return 'draft'; // 草稿状态
}
return 'published';
}
3.3 JSON数据的兼容性
TypeScript
// JSON支持null,序列化时会移除undefined
const data = {
value: null, // 保留
other: undefined // 被移除
};
const json = JSON.stringify(data);
console.log(json); // "{"value":null}"
四、实战决策指南
4.1 代码风格选择
推荐方案:undefined优先
TypeScript
// ✅ 符合TypeScript生态
interface Options {
maxRetries?: number; // 可选配置
timeout: number | undefined; // 必须存在但可为空
}
null专用场景:
TypeScript
// ✅ 明确的空对象语义
class LinkedList<T> {
value: T;
next: LinkedList<T> | null = null; // 链表终点
}
// ✅ 区分两种状态
interface APIResponse<T> {
data: T | null; // null = 明确无数据
error?: string; // undefined = 无错误
}
4.2 类型守卫最佳实践
TypeScript
function processValue(input: string | null | undefined) {
// 同时排除null和undefined
if (input == null) {
throw new Error('Value is required');
}
// 现在input被收窄为string
return input.toUpperCase();
}
// 精确判断
function strictCheck(value: string | null | undefined) {
if (value === null) {
// 处理明确的null
} else if (value === undefined) {
// 处理未定义
} else {
// value是string
}
}
五、严格模式下的终极建议
5.1 配置tsconfig.json
TypeScript
{
"compilerOptions": {
"strict": true,
"strictNullChecks": true,
"noImplicitReturns": true
}
}
5.2 团队规范示例
TypeScript
// .eslintrc.js
module.exports = {
rules: {
'@typescript-eslint/no-null': ['error', { allowNullable: true }],
'@typescript-eslint/prefer-nullish-coalescing': 'error'
}
}
// 推荐使用空值合并运算符
const timeout = config.timeout ?? 5000; // 仅对undefined生效
5.3 实用工具类型
TypeScript
// 确保非空类型
type NonEmpty<T> = T extends null | undefined ? never : T;
function assertDefined<T>(value: T): asserts value is NonEmpty<T> {
if (value === null || value === undefined) {
throw new Error('Value must be defined');
}
}
六、总结
在TypeScript严格模式中:
表格
复制
| 维度 | undefined | null |
|---|---|---|
| 默认选择 | ✅ 优先使用 | ❌ 特定场景使用 |
| 语法兼容性 | 可选参数/属性必需 | 需显式联合类型 |
| 业务语义 | 未设置、缺失 | 明确的空状态 |
| 生态系统 | 天然支持 | 需主动声明 |
黄金法则 :始终开启strictNullChecks,默认使用undefined处理可选值,仅在需要区分"空状态"与"未设置"或兼容外部API时,才祭出null这个精准工具。这样既能享受TypeScript类型系统的强大保护,又能保持代码的清晰意图。
后记 :TypeScript 3.7引入的可选链和空值合并进一步强化了undefined的地位,它们只处理undefined和null,这印证了两者在现代TypeScript中的核心角色。