TypeScript严格模式下的undefined与null

在TypeScript开发中,有两个"空值"概念让无数开发者困惑:undefinednull。虽然在JavaScript中它们都可表示"无值",但在TypeScript严格模式下,它们的区别至关重要。本文将深入剖析这对双胞胎的本质差异,并提供实战中明确的决策指南。

一、核心区别:不只是两个不同的值

1.1 类型系统层面的根本差异

在开启strictNullChecks的严格模式下,undefinednull是完全独立的类型:

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的地位,它们只处理undefinednull,这印证了两者在现代TypeScript中的核心角色。

相关推荐
美酒没故事°6 小时前
vue3拖拽+粘贴的综合上传器
前端·javascript·typescript
南村群童欺我老无力.8 小时前
Flutter 框架跨平台鸿蒙开发 - 打造安全可靠的密码生成器,支持强度检测与历史记录
flutter·华为·typescript·harmonyos
南村群童欺我老无力.13 小时前
Flutter 框架跨平台鸿蒙开发 - 开发二维码生成器与扫描器
flutter·华为·typescript·harmonyos
VT.馒头20 小时前
【力扣】2637. 有时间限制的 Promise 对象
前端·javascript·leetcode·typescript
VT.馒头20 小时前
【力扣】2631. 分组
javascript·算法·leetcode·typescript
yyt3630458411 天前
TypeScript { [key: string]: unknown } 索引签名写法和 Record 替代
前端·javascript·vue.js·typescript·ecmascript·es6
前端小趴菜051 天前
TypeScript
前端·typescript
Irene19912 天前
Vue3 中 PropType 的使用指南
typescript·proptype
孟无岐2 天前
【Laya】Animator2D 使用指南
typescript·游戏引擎·游戏程序·laya
web小白成长日记2 天前
从零起步,用TypeScript写一个Todo App:踩坑与收获分享
前端·javascript·typescript