TypeScript中的`null`与`undefined`

在TypeScript中,nullundefined是两种特殊的原始数据类型,它们都用于表示某个值的不存在。理解它们之间的区别以及如何在类型系统中正确处理它们,对于编写健壮的TypeScript代码至关重要。

核心概念:null vs undefined

特性 undefined null
类型 所有类型的默认值 空对象引用
定义 变量声明但未赋值 开发者显式设置的值
检测方法 === undefined 或严格检查 === null
典型来源 函数的未提供参数、对象缺少属性 明确设置"无值"、DOM操作
类型系统行为 可赋值给任何类型(默认) 可赋值给任何类型(默认)

启用严格空检查:必备第一步

tsconfig.json中启用严格空检查:

json 复制代码
{
  "compilerOptions": {
    "strictNullChecks": true, // 必需启用
    "strict": true            // 推荐启用所有严格模式
  }
}

启用前后的差异

typescript 复制代码
// 未启用严格空检查
let value: string;
value = null;      // ✅ 允许
value = undefined; // ✅ 允许

// 启用严格空检查后
let safeValue: string;
safeValue = null;      // ❌ 不能将类型"null"分配给类型"string"
safeValue = undefined; // ❌ 不能将类型"undefined"分配给类型"string"

启用严格空检查后,编译器将会阻止潜在的nullundefined导致的运行时错误。

明确处理null和undefined

1. 联合类型:安全表示可选值

typescript 复制代码
type MaybeString = string | null | undefined;

function processInput(input: MaybeString): void {
  if (input === null) {
    console.log("输入为null");
  } else if (input === undefined) {
    console.log("输入未定义");
  } else {
    console.log(`输入内容: ${input.toUpperCase()}`);
  }
}

2. 可选参数和可选属性

typescript 复制代码
// 函数可选参数(隐含 | undefined)
function greet(name?: string) {
  console.log(`Hello, ${name || "Anonymous"}!`);
}

// 接口可选属性
interface UserProfile {
  username: string;
  email?: string; // 相当于 string | undefined
}

const newUser: UserProfile = { username: "jsmith" }; // email可以是undefined

3. 空值守卫和类型收窄

typescript 复制代码
function getLength(value: string | null | undefined): number {
  // 类型守卫
  if (value === null || value === undefined) {
    return 0;
  }
  return value.length; // 这里value被收窄为string
}

// 更简洁的写法 - 空值合并运算符
const safeGetLength = (value: string | null | undefined): number => 
  value?.length ?? 0;

实用操作符

typescript 复制代码
interface Product {
  id: number;
  details?: {
    price?: number;
    manufacturer?: {
      name: string;
      country: string;
    };
  };
}

const product: Product = getProduct();

// 安全访问嵌套属性
const country = product.details?.manufacturer?.country; // string | undefined

// 安全调用方法
const uppercaseCountry = product.details?.manufacturer?.country?.toUpperCase();

2. 空值合并运算符 ??(Default Value)

typescript 复制代码
const DEFAULT_PORT = 3000;

// 仅当左侧为null或undefined时使用右侧值
const port = config.port ?? DEFAULT_PORT;

// 不同于 || 运算符(会忽略所有假值)
const count = 0;
console.log(count || 10); // 10(0是假值)
console.log(count ?? 10); // 0(0不是null或undefined)

3. 空断言运算符 !(谨慎使用)

typescript 复制代码
function mustExistValue(value: string | null): string {
  // 开发者确保不会为null
  return value!;
}

// 在严格知道值的来源时使用
const element = document.getElementById("app")!;
element.innerHTML = "Loaded";

实际应用场景

1. API响应处理

typescript 复制代码
interface ApiResponse<T> {
  success: boolean;
  data?: T;        // 成功时有数据
  error?: string;  // 错误时有消息
}

function handleResponse(response: ApiResponse<{ user: string }>) {
  if (response.success) {
    console.log(`User: ${response.data?.user}`); // 安全访问
  } else {
    console.error(`Error: ${response.error ?? 'Unknown error'}`);
  }
}

2. 配置对象默认值

typescript 复制代码
interface AppConfig {
  port: number;
  logLevel?: 'info' | 'warn' | 'error';
  maxConnections?: number;
}

function setupApp(config: AppConfig) {
  const finalConfig: Required<AppConfig> = {
    port: config.port,
    logLevel: config.logLevel ?? 'info',
    maxConnections: config.maxConnections ?? 100
  };
}

3. 安全数据转换

typescript 复制代码
interface UserInput {
  id?: string;
  name?: string | null;
  age?: number;
}

type ValidatedUser = {
  id: string;
  name: string;
  age: number;
}

function validateUser(input: UserInput): ValidatedUser | null {
  if (!input.id || !input.name || input.age === undefined) {
    return null;
  }
  
  return {
    id: input.id,
    name: input.name, // 已排除null和undefined
    age: input.age
  };
}

高级模式:null对象模式

typescript 复制代码
// 定义空对象实现
interface Logger {
  log(message: string): void;
}

class ConsoleLogger implements Logger {
  log(message: string): void {
    console.log(message);
  }
}

class NullLogger implements Logger {
  log(_: string): void {
    // 什么都不做
  }
}

function getLogger(config: { debug?: boolean }): Logger {
  return config.debug ? new ConsoleLogger() : new NullLogger();
}

// 使用时无需空值检查
const logger = getLogger({ debug: false });
logger.log("This won't be printed"); // 安全调用

严格空检查下的最佳实践

  1. 显式声明可选性

    typescript 复制代码
    // 避免模糊的null或undefined声明
    function sendMessage(
      content: string,
      recipient: string | null // 明确可能为null
    ) {
      // ...
    }
  2. 优先使用联合类型

    typescript 复制代码
    // 优于any或禁用检查
    type SearchResult = 
      | { status: 'found'; data: string }
      | { status: 'not-found'; errorCode: number };
  3. 空值判断前置

    typescript 复制代码
    function process(data: string | null) {
      if (data === null) throw new Error("无效数据");
      
      // 之后所有代码都可以安全使用data
      const len = data.length;
    }
  4. 避免非空断言滥用

    diff 复制代码
    -  const value = someFunction()!; 
    + if (!someFunction()) throw new Error();
    + const value = someFunction();
  5. 使用实用类型简化

    typescript 复制代码
    // 内置实用类型
    type Nullable<T> = T | null;
    type Optional<T> = T | undefined;
    
    // 使用示例
    let phoneNumber: Nullable<string> = null;
    let middleName: Optional<string> = undefined;

与其他语言的空安全对比

语言 空安全策略 类似特性
TypeScript 严格空检查+联合类型 可选链?.、空合并??
Kotlin 内置空安全系统 Elvis运算符?:
Swift Optional类型 ??运算符、if let解包
C# 8+ 可空引用类型 ?.运算符、!后缀
Java Optional包装类 optional.orElse()方法

常见陷阱与解决方案

陷阱1:忽略严格空检查

typescript 复制代码
// 未启用严格空检查的代码
const element = document.getElementById("myElement");
element.innerHTML = "更新内容"; // 运行时可能报错

解决方案

typescript 复制代码
const element = document.getElementById("myElement");
if (element) {
  element.innerHTML = "更新内容";
}

陷阱2:混淆||??

typescript 复制代码
const count = parseInt(queryParam) || 0; // 0值会被覆盖

解决方案

typescript 复制代码
const count = parseInt(queryParam) ?? 0; // 正确处理0值

陷阱3:滥用非空断言

typescript 复制代码
function getFirstUser(users: User[] | null) {
  return users![0]; // 危险!
}

解决方案

typescript 复制代码
function getFirstUser(users: User[] | null) {
  if(!users || users.length === 0) {
    throw new Error("用户列表为空");
  }
  return users[0];
}

掌控空值的4项原则

  1. 启用严格空检查:这是所有安全处理的基础
  2. 显式声明空值可能性:使用联合类型明确表达意图
  3. 善用现代操作符?.?? 是你的有力武器
  4. 避免空断言滥用 :只有在绝对确定时才使用!

"在TypeScript中,nullundefined不是错误,而是需要明确处理的状态。将它们从类型系统中排除不是目标,而是将它们作为类型设计的一部分加以整合。" - TypeScript核心设计原则

通过合理运用TypeScript的类型系统和空值处理工具,你可以构建出既灵活又安全的应用程序,有效避免Cannot read property of undefined等常见运行时错误,提升代码的健壮性和可维护性。

拓展学习

相关推荐
超人不会飛21 分钟前
就着HTTP聊聊SSE的前世今生
前端·javascript·http
蓝胖子的多啦A梦24 分钟前
Vue+element 日期时间组件选择器精确到分钟,禁止选秒的配置
前端·javascript·vue.js·elementui·时间选选择器·样式修改
夏天想27 分钟前
vue2+elementui使用compressorjs压缩上传的图片
前端·javascript·elementui
今晚打老虎z35 分钟前
dotnet-env: .NET 开发者的环境变量加载工具
前端·chrome·.net
用户38022585982440 分钟前
vue3源码解析:diff算法之patchChildren函数分析
前端·vue.js
烛阴1 小时前
XPath 进阶:掌握高级选择器与路径表达式
前端·javascript
小鱼小鱼干1 小时前
【JS/Vue3】关于Vue引用透传
前端
JavaDog程序狗1 小时前
【前端】HTML+JS 实现超燃小球分裂全过程
前端
独立开阀者_FwtCoder1 小时前
URL地址末尾加不加 "/" 有什么区别
前端·javascript·github
独立开阀者_FwtCoder1 小时前
Vue3 新特性:原来watch 也能“暂停”和“恢复”了!
前端·javascript·github