TypeScript 特殊类型与空值安全

本文献给:

已掌握 TypeScript 基础类型(string、number、boolean、数组、元组、枚举)的开发者。本文将带你探索 TypeScript 中更特殊的类型,理解它们的设计初衷与使用场景,并掌握处理空值的安全技巧。

你将学到:

  1. any 的弊端与 unknown 的安全用法
  2. voidnever 的区别及适用场景
  3. nullundefinedstrictNullChecks 下的行为
  4. 可选链 ?. 与空值合并 ?? 的使用
  5. 非空断言 ! 的风险与正确使用方式

目录

  • [一、any ------ 危险的逃生舱](#一、any —— 危险的逃生舱)
    • [1.1 什么是 any?](#1.1 什么是 any?)
    • [1.2 any 的弊端](#1.2 any 的弊端)
    • [1.3 何时可以容忍 any?](#1.3 何时可以容忍 any?)
  • [二、unknown ------ 安全的 any](#二、unknown —— 安全的 any)
    • [2.1 unknown 的特性](#2.1 unknown 的特性)
    • [2.2 unknown 的使用场景](#2.2 unknown 的使用场景)
    • [2.3 unknown 与 any 对比](#2.3 unknown 与 any 对比)
  • [三、void 与 never ------ 函数的"无返回值"](#三、void 与 never —— 函数的“无返回值”)
    • [3.1 void ------ 没有返回值的函数](#3.1 void —— 没有返回值的函数)
    • [3.2 never ------ 永远不会返回的函数](#3.2 never —— 永远不会返回的函数)
    • [3.3 void 与 never 的区别](#3.3 void 与 never 的区别)
      • [never 在类型守卫中的妙用](#never 在类型守卫中的妙用)
  • [四、null 与 undefined ------ 空值处理](#四、null 与 undefined —— 空值处理)
    • [4.1 严格空值检查(strictNullChecks)](#4.1 严格空值检查(strictNullChecks))
    • [4.2 联合类型与空值](#4.2 联合类型与空值)
    • [4.3 可选链(Optional Chaining)`?.`](#4.3 可选链(Optional Chaining)?.)
    • [4.4 空值合并(Nullish Coalescing)`??`](#4.4 空值合并(Nullish Coalescing)??)
    • [4.5 非空断言(Non-null Assertion)`!`](#4.5 非空断言(Non-null Assertion)!)
      • [⚠️ 风险](#⚠️ 风险)
    • [4.6 空值处理最佳实践](#4.6 空值处理最佳实践)
  • 五、常见错误与注意事项
    • [5.1 误用 any 导致类型失效](#5.1 误用 any 导致类型失效)
    • [5.2 将 void 与 undefined 混用](#5.2 将 void 与 undefined 混用)
    • [5.3 非空断言滥用](#5.3 非空断言滥用)
    • [5.4 可选链与空值合并的混用陷阱](#5.4 可选链与空值合并的混用陷阱)
  • 六、综合示例
  • 七、小结

一、any ------ 危险的逃生舱

1.1 什么是 any?

any 是 TypeScript 中的顶级类型 ,可以表示任意值。被标记为 any 的变量会关闭所有类型检查,相当于回到了 JavaScript 的动态类型。

typescript 复制代码
let value: any = "hello";
value = 42;           // OK
value = true;         // OK
value.toUpperCase();  // OK(运行时可能出错)

1.2 any 的弊端

any 虽然灵活,但会破坏 TypeScript 的类型安全保障:

typescript 复制代码
let data: any = { name: "Alice" };
console.log(data.age.toFixed());  // ❌ 编译通过,运行时报错(Cannot read property 'toFixed' of undefined)

any 还会污染 其他类型,因为任何赋值都会导致接收方也变成 any 或失去精确类型:

typescript 复制代码
let dangerous: any = "text";
let safe: number = dangerous;   // 不报错,但 safe 实际是 string,后续使用可能出问题

1.3 何时可以容忍 any?

  • 快速原型开发或迁移旧代码时临时使用
  • 第三方库没有类型定义且无法立即补全时
  • 某些极其复杂的动态场景,类型难以表达

尽量用 unknown 替代 any,保持类型安全。

二、unknown ------ 安全的 any

2.1 unknown 的特性

unknown 是 TypeScript 3.0 引入的安全类型 ,表示"不知道是什么类型"。与 any 不同,unknown 的值不能直接使用,必须经过类型收窄(narrowing)后才能操作。

typescript 复制代码
let value: unknown = "hello";
value = 42;           // OK
value = true;         // OK

// 直接使用会报错
value.toUpperCase();  // ❌ 类型 unknown 没有 toUpperCase 方法

// 必须先收窄类型
if (typeof value === "string") {
    console.log(value.toUpperCase());  // OK
}

2.2 unknown 的使用场景

  • any 迁移时,先用 unknown 替代,强制后续收窄
  • 处理 API 响应、用户输入等不确定来源的数据
  • 编写通用工具函数,要求使用者显式处理类型
typescript 复制代码
function safeParse(input: string): unknown {
    return JSON.parse(input);
}

const result = safeParse('{"name":"Alice"}');
// 不能直接访问 result.name,需要收窄
if (typeof result === "object" && result !== null && "name" in result) {
    console.log(result.name);  // OK
}

2.3 unknown 与 any 对比

特性 any unknown
可赋值给任何类型 ❌(需先收窄)
可调用方法或属性 ✅(编译通过) ❌(编译报错)
安全性 低(绕过类型检查) 高(强制类型收窄)
适用场景 快速原型、遗留代码 类型未知但需安全处理

三、void 与 never ------ 函数的"无返回值"

3.1 void ------ 没有返回值的函数

void 表示一个函数没有返回值 ,或者返回 undefined(在严格模式下)。

typescript 复制代码
function logMessage(msg: string): void {
    console.log(msg);
    // 没有 return,或 return; 或 return undefined;
}

变量也可以声明为 void,但只能赋值为 undefinednull(非严格模式):

typescript 复制代码
let unusable: void = undefined;
unusable = null;  // 仅在 strictNullChecks: false 时允许

注意 :在回调函数中,如果期望函数不返回值,应标注 void,而不是 undefined(因为 undefined 表示必须返回 undefined)。

3.2 never ------ 永远不会返回的函数

never 表示永远不会正常结束的函数,例如:

  • 抛出异常(函数执行被中断)
  • 无限循环
typescript 复制代码
function throwError(message: string): never {
    throw new Error(message);
}

function infiniteLoop(): never {
    while (true) {}
}

never 是所有类型的子类型,可以赋值给任何类型;但没有类型是 never 的子类型(除了 never 自身)。

3.3 void 与 never 的区别

特性 void never
含义 函数正常结束,无返回值 函数永远不会正常结束
可赋值的值 undefinednull 无(无法正常返回)
使用场景 无返回值的函数(如事件处理) 异常抛出、无限循环、类型守卫的穷尽检查

never 在类型守卫中的妙用

switch 或条件判断中,可以用 never穷尽检查

typescript 复制代码
type Shape = "circle" | "square";

function area(shape: Shape) {
    switch (shape) {
        case "circle":
            return Math.PI * 1 ** 2;
        case "square":
            return 1 * 1;
        default:
            const _exhaustiveCheck: never = shape;  // 如果 shape 类型被扩展,这里会报错
            return _exhaustiveCheck;
    }
}

四、null 与 undefined ------ 空值处理

4.1 严格空值检查(strictNullChecks)

TypeScript 的 strictNullChecks 选项(默认开启)严格区分 nullundefined 和其他类型。

  • 开启后,nullundefined 只能赋值给 anyunknown 或它们自身的类型。
  • 变量声明时如果不包含 null/undefined,则不能赋值为这些空值。
typescript 复制代码
// strictNullChecks: true
let name: string = "Alice";
name = null;      // ❌ 不能将 null 赋给 string
name = undefined; // ❌

let age: number | null = 30;
age = null;       // OK

4.2 联合类型与空值

常见模式是用联合类型允许 nullundefined

typescript 复制代码
let maybeName: string | null = "Alice";
maybeName = null;   // OK

let maybeAge: number | undefined = 25;
maybeAge = undefined; // OK

4.3 可选链(Optional Chaining)?.

访问深层属性时,如果中间某个属性可能为 nullundefined,使用 ?. 可以安全地短路返回 undefined

typescript 复制代码
interface User {
    address?: {
        city?: string;
    };
}

let user: User = {};

// 传统方式
let city = user.address && user.address.city;  // undefined

// 可选链
let city2 = user.address?.city;  // undefined

可选链也支持函数调用和数组索引:

typescript 复制代码
let result = obj.method?.();   // 如果 method 不存在,返回 undefined
let item = arr?.[0];           // 如果 arr 是 null/undefined,返回 undefined

4.4 空值合并(Nullish Coalescing)??

?? 运算符用于在左侧值为 nullundefined 时返回右侧值,与 || 不同,它不会将 0""false 视为假值。

typescript 复制代码
let value1 = null ?? "default";   // "default"
let value2 = undefined ?? "default"; // "default"
let value3 = 0 ?? "default";      // 0(不是 null/undefined,保留原值)
let value4 = "" ?? "default";     // ""(同上)

// 对比 ||
let value5 = 0 || "default";      // "default"(|| 会将 0 视为假值)

4.5 非空断言(Non-null Assertion)!

当 TypeScript 无法确定一个值不为 nullundefined,但开发者确信它一定存在时,可以使用 ! 后缀断言。

typescript 复制代码
let element = document.getElementById("app")!;  // 断言该元素一定存在
element.innerHTML = "Hello";

// 也可以用于属性访问
interface User {
    name?: string;
}
let user: User = { name: "Alice" };
let name = user.name!.toUpperCase();  // 断言 name 存在

⚠️ 风险

! 只是编译时断言,不会生成运行时检查。如果断言错误,运行时仍可能抛出 Cannot read property of undefined。应谨慎使用,优先考虑可选链或类型守卫。

4.6 空值处理最佳实践

场景 推荐方式
访问可能不存在的深层属性 可选链 ?.
为 null/undefined 提供默认值 空值合并 ??
确信某个值非空但 TypeScript 不认可 先用 ?. 或类型守卫,最后才考虑 !
函数参数或返回值允许空值 显式使用联合类型 `string
类型守卫收窄 if (value !== null) { ... }typeof

五、常见错误与注意事项

5.1 误用 any 导致类型失效

typescript 复制代码
let data: any = fetchData();
let result = data.user.name;  // 无报错,但可能运行时崩溃

解决 :用 unknown + 类型守卫。

5.2 将 void 与 undefined 混用

typescript 复制代码
function fn(): void {
    return undefined;  // OK
}
function fn2(): void {
    return null;       // ❌ 严格模式下不能返回 null
}

5.3 非空断言滥用

typescript 复制代码
let maybeNum: number | null = Math.random() > 0.5 ? 42 : null;
let definitelyNum = maybeNum!;  // 如果为 null,运行时错误

解决 :使用 if (maybeNum !== null)??

5.4 可选链与空值合并的混用陷阱

typescript 复制代码
let val = obj?.prop ?? "default";
// 如果 obj 不存在,obj?.prop 返回 undefined,触发 ??
// 如果 obj.prop 存在但值为 null,也会触发 ??

六、综合示例

typescript 复制代码
// 模拟 API 响应
interface ApiResponse<T> {
    data?: T;
    error?: string;
}

// 处理不确定的响应
async function fetchUser(): Promise<ApiResponse<{ name: string }>> {
    // 模拟随机成功或失败
    const success = Math.random() > 0.5;
    if (success) {
        return { data: { name: "Alice" } };
    } else {
        return { error: "Network error" };
    }
}

async function handleUser() {
    const response = await fetchUser();
    
    // 使用 unknown 安全处理
    const unknownData: unknown = response.data;
    
    // 类型收窄
    if (unknownData && typeof unknownData === "object" && "name" in unknownData) {
        console.log(unknownData.name);  // 类型推断为 string
    } else {
        console.log(response.error ?? "Unknown error");
    }
    
    // 可选链与空值合并示例
    const userName = response.data?.name ?? "Guest";
    console.log(`User: ${userName}`);
}

// never 示例:类型守卫穷尽检查
type Status = "pending" | "success" | "error";

function getStatusMessage(status: Status): string {
    switch (status) {
        case "pending":
            return "Loading...";
        case "success":
            return "Success!";
        case "error":
            return "Failed.";
        default:
            const _exhaustive: never = status;  // 如果 Status 增加新值,这里会报错
            return _exhaustive;
    }
}

七、小结

类型 含义 使用场景
any 任意类型,关闭检查 临时逃生舱,尽量少用
unknown 未知类型,强制收窄 处理不确定数据,更安全
void 无返回值 无返回值的函数
never 永不返回 异常、无限循环、穷尽检查
null 空值 明确表示"没有值"
undefined 未定义 未初始化的变量、缺失属性
?. 可选链 安全访问深层属性
?? 空值合并 为 null/undefined 提供默认值
! 非空断言 确信值非空时使用,需谨慎

觉得文章有帮助?别忘了:

👍 点赞 👍 -- 给我一点鼓励
⭐ 收藏 ⭐ -- 方便以后查看
🔔 关注 🔔 -- 获取更新通知


标签: #TypeScript #空值处理 #前端开发

相关推荐
EasyDSS2 小时前
企业级私有化部署视频直播点播平台EasyDSS如何构建企业远程会议安全防线
安全·音视频
dgw26486338093 小时前
深信服数据传输安全-NPN-(1)
安全
2401_832298103 小时前
OpenClaw 3.28 终章:从 “激进重构” 到 “稳健治理”,AI 智能体安全与体验的平衡之道
人工智能·安全·重构
EasyGBS3 小时前
国标GB28181视频分析平台EasyGBS视频质量诊断助力能源矿山行业实现安全高效管控体系
安全·音视频·能源
她说..3 小时前
Spring单例Bean线程安全问题 深度解析
java·后端·安全·spring·springboot
桌面运维家3 小时前
Windows防火墙高级配置:网络安全深度优化
windows·安全·web安全
147API3 小时前
Claude Code 新增「计算机使用」能力:架构解析、自动化场景与安全风险避坑
运维·安全·自动化·claude
亚远景aspice3 小时前
AI深度融入汽车研发合规 亚远景引领行业AI升级
安全·汽车
LlNingyu3 小时前
文艺复兴,什么是CSRF,常见形式(二)--SameSite属性
前端·网络·安全·web安全·csrf