javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > TypeScript特殊类型与空值安全

TypeScript特殊类型与空值安全详解

作者:烛衔溟

本文介绍了TypeScript中advanced types,包括any、unknown、void、never、null、undefined等类型,详细讲解了它们的设计初衷、使用场景及注意事项,帮助开发者更好地理解和使用这些类型,提高代码的安全性和可读性

本文献给:

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

你将学到:

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

一、any —— 危险的逃生舱

1.1 什么是 any?

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

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

1.2 any 的弊端

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

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

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

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)后才能操作。

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 的使用场景

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 对比

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

三、void 与 never —— 函数的“无返回值”

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

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

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

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

let unusable: void = undefined;
unusable = null;  // 仅在 strictNullChecks: false 时允许

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

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

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

function throwError(message: string): never {
    throw new Error(message);
}

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

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

3.3 void 与 never 的区别

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

never 在类型守卫中的妙用

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

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 和其他类型。

// strictNullChecks: true
let name: string = "Alice";
name = null;      // ❌ 不能将 null 赋给 string
name = undefined; // ❌

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

4.2 联合类型与空值

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

let maybeName: string | null = "Alice";
maybeName = null;   // OK

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

4.3 可选链(Optional Chaining)?.

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

interface User {
    address?: {
        city?: string;
    };
}

let user: User = {};

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

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

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

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

4.4 空值合并(Nullish Coalescing)??

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

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,但开发者确信它一定存在时,可以使用 ! 后缀断言。

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 | null
类型守卫收窄if (value !== null) { ... } 或 typeof

五、常见错误与注意事项

5.1 误用 any 导致类型失效

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

解决:用 unknown + 类型守卫。

5.2 将 void 与 undefined 混用

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

5.3 非空断言滥用

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

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

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

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

六、综合示例

// 模拟 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 提供默认值
!非空断言确信值非空时使用,需谨慎

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文