编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

  • iOS开发和进阶

  • Web开发和进阶

    • README
    • HTML工具手册

    • CSS样式与布局

    • JavaScript核心

    • TypeScript入门

      • README
      • TS类型系统基础
      • TS接口与对象类型
      • TS函数与类实战
      • TS泛型编程实战
      • TS高级类型编程
      • TS类型守卫机制
        • 1. 案例引入
          • 1.1 联合类型——编译器"看得见"但"摸不准"
          • 1.2 类型收窄——联合类型的唯一正解
        • 2. typeof 守卫——区分基本类型
          • 2.1 支持的类型字符串
          • 2.2 typeof 的局限性
        • 3. instanceof 守卫——判断类的实例
        • 4. in 关键字守卫——判断属性存在
        • 5. 自定义类型守卫——is 关键字
          • 5.1 基础语法
          • 5.2 自定义守卫的实战价值
        • 6. 等价与赋值收窄
          • 6.1 严格等值收窄
          • 6.2 赋值收窄
        • 7. 判别联合(Discriminated Union)
          • 7.1 给每个成员一个"类型标签"
          • 7.2 通过判别属性收窄
          • 7.3 与穷尽性检查结合
        • 8. never 与穷尽性检查
          • 8.1 never 作为"不可能"的守卫
        • 9. unknown vs any:最后的防线
          • 9.1 any——放弃一切检查
          • 9.2 unknown——安全的万能类型
          • 9.3 选型决策
        • 10. 实战:结果类型模式(Result Pattern)
        • 11. 速查表
      • TS模块系统详解
      • TS工程配置实践实践
    • Vue高级进阶

    • Web工程化实践

  • Linux应用开发

  • IoT智能硬件开发

  • Apps
  • Web开发和进阶
  • TypeScript入门
杨充
2025-06-24
目录

TS类型守卫机制

# 06.TS类型守卫机制

从联合类型到精确类型——TS 如何在运行时"精确判断"类型?掌握类型守卫,让动态类型安全落地。

# 1. 案例引入

# 1.1 联合类型——编译器"看得见"但"摸不准"

type Shape = Circle | Rectangle | Triangle;

function area(shape: Shape): number {
    // shape.radius;  // ❌ Shape 上不存在 radius——可能是 Rectangle
    // shape.width;   // ❌ 同理

    // TS 知道你传的是 Shape,但不知道此刻具体是哪个
    // 需要你"告诉"编译器:在某个分支里,它是什么
}

# 1.2 类型收窄——联合类型的唯一正解

function area(shape: Shape): number {
    if ("radius" in shape) {
        // shape 被收窄为 Circle
        return Math.PI * shape.radius ** 2;
    }
    if ("width" in shape) {
        // shape 被收窄为 Rectangle
        return shape.width * shape.height;
    }
    // shape 被收窄为 Triangle
    return 0.5 * shape.base * shape.height;
}

类型守卫的本质:在运行时告诉编译器"这个分支里,变量的类型已收窄为 X"。


# 2. typeof 守卫——区分基本类型

# 2.1 支持的类型字符串

// typeof 返回的 JS 类型字符串(7种)
// "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"

function process(value: string | number | boolean) {
    if (typeof value === "string") {
        console.log(value.toUpperCase());  // value: string
    } else if (typeof value === "number") {
        console.log(value.toFixed(2));     // value: number
    } else {
        console.log(value);                // value: boolean
    }
}

# 2.2 typeof 的局限性

// typeof null === "object" —— 不会将 null 收窄为 null
function test(x: string | null) {
    if (typeof x === "object") {
        // x 是 object | null —— 而不是 null!
        // x.length;  // ❌
    }
}

// 正确做法:直接用 === null 或 === undefined
function test2(x: string | null) {
    if (x === null) {
        console.log("null");
    } else {
        console.log(x.length);  // ✅ x: string
    }
}

# 3. instanceof 守卫——判断类的实例

class User {
    constructor(public name: string) {}
    greet() { return `Hi, ${this.name}`; }
}

class Bot {
    constructor(public id: number) {}
    respond() { return `Bot #${this.id}`; }
}

function interact(entity: User | Bot) {
    if (entity instanceof User) {
        entity.greet();           // ✅ entity: User
    } else {
        entity.respond();         // ✅ entity: Bot
    }
}

# 4. in 关键字守卫——判断属性存在

interface Circle {
    kind: "circle";
    radius: number;
}

interface Square {
    kind: "square";
    side: number;
}

type Shape = Circle | Square;

function area(shape: Shape) {
    if ("radius" in shape) {
        // shape: Circle —— 因为只有 Circle 有 radius
        return Math.PI * shape.radius ** 2;
    } else {
        // shape: Square
        return shape.side ** 2;
    }
}

# 5. 自定义类型守卫——is 关键字

# 5.1 基础语法

// 语法:param is Type
// 返回值必须是 boolean

// 判断是否为字符串数组
function isStringArray(value: unknown): value is string[] {
    return Array.isArray(value) && value.every(item => typeof item === "string");
}

const data: unknown = ["a", "b", "c"];

if (isStringArray(data)) {
    // data 被收窄为 string[]
    console.log(data.map(s => s.toUpperCase()));
}

# 5.2 自定义守卫的实战价值

// 场景:API 返回的数据结构校验
interface User {
    id: number;
    name: string;
    email: string;
}

// 类型守卫——运行时校验数据是否符合 User 结构
function isUser(obj: unknown): obj is User {
    if (typeof obj !== "object" || obj === null) return false;

    const u = obj as Record<string, unknown>;
    return (
        typeof u.id === "number" &&
        typeof u.name === "string" &&
        typeof u.email === "string"
    );
}

// 使用
async function fetchUser(): Promise<unknown> {
    const res = await fetch("/api/user");
    return res.json();
}

const raw = await fetchUser();
if (isUser(raw)) {
    console.log(raw.name);  // ✅ raw: User
}

# 6. 等价与赋值收窄

# 6.1 严格等值收窄

function example(x: string | number, y: string | boolean) {
    if (x === y) {
        // x 和 y 都被收窄为 string(唯一共有类型)
        console.log(x.toUpperCase());
    }
}

# 6.2 赋值收窄

let x = Math.random() > 0.5 ? "hello" : 42;
// x: string | number

x = "world";
// x: string —— TS 跟踪了赋值的"最新类型"
console.log(x.toUpperCase());  // ✅

# 7. 判别联合(Discriminated Union)

# 7.1 给每个成员一个"类型标签"

// 为联合类型的每个成员添加一个字面量类型的"判别字段"
interface Circle {
    kind: "circle";    // ← 判别属性
    radius: number;
}

interface Square {
    kind: "square";    // ← 判别属性
    side: number;
}

interface Triangle {
    kind: "triangle";  // ← 判别属性
    base: number;
    height: number;
}

type Shape = Circle | Square | Triangle;

# 7.2 通过判别属性收窄

function area(shape: Shape): number {
    switch (shape.kind) {
        case "circle":
            return Math.PI * shape.radius ** 2;   // shape: Circle
        case "square":
            return shape.side ** 2;                // shape: Square
        case "triangle":
            return 0.5 * shape.base * shape.height; // shape: Triangle
    }
}

# 7.3 与穷尽性检查结合

// 确保覆盖所有形状
function assertExhaustive(x: never): never {
    throw new Error(`未处理的类型: ${x}`);
}

function areaExhaustive(shape: Shape): number {
    switch (shape.kind) {
        case "circle":
            return Math.PI * shape.radius ** 2;
        case "square":
            return shape.side ** 2;
        case "triangle":
            return 0.5 * shape.base * shape.height;
        default:
            // 如果将来新增了 Rectangle 但没有添加 case,这里编译报错
            return assertExhaustive(shape);
    }
}

# 8. never 与穷尽性检查

# 8.1 never 作为"不可能"的守卫

type Result = 
    | { status: "ok"; data: string }
    | { status: "error"; message: string };

function handle(r: Result) {
    if (r.status === "ok") {
        console.log(r.data);
        return;
    }
    if (r.status === "error") {
        console.log(r.message);
        return;
    }

    // 这里 r 的剩余类型是 never
    // 当新增一个 status 分支时,这里的 never 就会变成新类型并报错
    const exhaustive: never = r;
}

# 9. unknown vs any:最后的防线

# 9.1 any——放弃一切检查

let danger: any = fetchSomeData();
danger.foo();            // ✅ 编译通过,运行时可能崩
danger.property.bar();   // ✅ 编译通过,运行时可能崩
const num: number = danger;  // ✅ any 污染一切

# 9.2 unknown——安全的万能类型

let safe: unknown = fetchSomeData();

// safe.foo();          // ❌ 不能直接使用
// safe.property.bar(); // ❌ 不能直接使用
// const num: number = safe;  // ❌ 不能赋值

// 必须先验证/收窄
if (typeof safe === "string") {
    console.log(safe.toUpperCase());  // ✅
} else if (Array.isArray(safe)) {
    console.log(safe.length);         // ✅
} else if (isUser(safe)) {
    console.log(safe.name);           // ✅
}

# 9.3 选型决策

你的场景是什么?
├── 从不可信的 API / 用户输入获取数据 → unknown + 类型守卫
├── 完全放弃类型检查(尽快验证的临时变量)→ any
└── 正常开发 → 永远不用 any 和 unknown

# 10. 实战:结果类型模式(Result Pattern)

// 像 Rust 的 Result 一样处理成功和失败
type Success<T> = { ok: true; data: T };
type Failure = { ok: false; error: string };
type Result<T> = Success<T> | Failure;

// 工厂函数
function ok<T>(data: T): Result<T> {
    return { ok: true, data };
}

function fail<T>(error: string): Result<T> {
    return { ok: false, error };
}

// 使用判别联合收窄
interface ApiUser { id: number; name: string; }

async function getUser(id: number): Promise<Result<ApiUser>> {
    try {
        const res = await fetch(`/api/user/${id}`);
        if (!res.ok) return fail("网络错误");
        const user: ApiUser = await res.json();
        return ok(user);
    } catch {
        return fail("未知错误");
    }
}

const result = await getUser(1);
if (result.ok) {
    console.log(result.data.name);  // ✅ data: ApiUser
} else {
    console.error(result.error);    // ✅ error: string
}

# 11. 速查表

守卫方式 语法 适用场景
typeof typeof x === "string" 基本类型(string/number/boolean等)
instanceof x instanceof MyClass 判断属于哪个类
in "prop" in x 判断属性是否存在
自定义守卫 x is Type 复杂结构校验
严格相等 x === y 两个联合类型取交集
判别属性 x.kind === "circle" 联合类型精确定位
赋值收窄 x = "hello" 跟踪最新赋值类型
穷尽性检查 never 确保 switch 全覆盖

守卫三原则:

  1. 能用 typeof 不用自定义守卫
  2. 能用判别联合不用 in 关键字
  3. 外部数据永远用 unknown + is 自定义守卫

下一篇:07.TS模块系统详解

上次更新: 2026/06/24, 12:59:24
TS高级类型编程
TS模块系统详解

← TS高级类型编程 TS模块系统详解→

最近更新
01
CSS选择器入门
06-23
02
CSS定位与层级
06-23
03
CSS盒模型详解
06-23
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式