Skip to content

条件类型与类型守卫

核心问题解析

问题一:类型也能“if判断”吗?

在 JavaScript 中,我们用 if 根据值做逻辑分支:

ts
if (typeof x === 'string') {
  console.log(x.toUpperCase()); // ✅ 此时 x 被识别为 string
}

TypeScript 的类型系统也支持类似的“逻辑判断”——这就是条件类型

答案:是的,通过 T extends U ? X : Y 语法,TS 支持类型层面的“三元运算符”。

ts
type IsString<T> = T extends string ? true : false;

type A = IsString<'hello'>;  // true
type B = IsString<42>;       // false

🔍 这就是“类型编程”中的“条件逻辑”,让类型能根据输入动态选择。


问题二:instanceofin 怎么影响类型?

看这个例子:

ts
class Dog { bark() { console.log('woof'); } }
class Cat { meow() { console.log('meow'); } }

function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark(); // ✅ 可调用
  } else {
    animal.meow(); // ✅ 可调用
  }
}

虽然 animal 类型是 Dog | Cat,但在 if 块内,TS 缩小了类型范围

📌 机制

  • instanceof:检查构造函数,TS 知道它能区分类实例
  • in 操作符:检查对象是否有某属性
ts
if ('bark' in animal) {
  animal.bark(); // ✅ TS 推断 animal: Dog
}

答案instanceofin 是 TS 内置的类型守卫(Type Guard),能在运行时检查的基础上,实现类型缩小(Narrowing)


问题三:为什么函数能“缩小”类型?

我们希望封装类型检查逻辑:

ts
function isDog(animal: Dog | Cat): boolean {
  return animal instanceof Dog;
}

但直接使用会失败:

ts
if (isDog(animal)) {
  animal.bark(); // ❌ 报错!TS 不知道 isDog 能缩小类型
}

解决方案:使用 is 类型谓词

ts
function isDog(animal: Dog | Cat): animal is Dog {
  return animal instanceof Dog;
}

现在:

ts
if (isDog(animal)) {
  animal.bark(); // ✅ 成功!类型被缩小为 Dog
}

📌 答案:通过 arg is Type 返回类型,我们告诉 TS:“这个函数的返回值为 true 时,参数 arg 的类型就是 Type”。


学习目标详解

目标一:掌握条件类型语法:T extends U ? X : Y

基本语法

ts
type TypeName<T> = 
  T extends string ? 'string' :
  T extends number ? 'number' :
  T extends boolean ? 'boolean' :
  'object';

type A = TypeName<'hello'>; // 'string'
type B = TypeName<true>;     // 'boolean'
type C = TypeName<{}>;       // 'object'

分布式条件类型

T 是联合类型时,条件类型会自动“分发”到每个成员:

ts
type Wrapped<T> = T extends any ? [T] : never;
type Result = Wrapped<string | number>; 
// 相当于 (string extends any ? [string] : never) | (number extends any ? [number] : never)
// 结果:[string] | [number]

⚠️ 使用 never 可过滤联合类型:

ts
type FilterString<T> = T extends string ? T : never;
type StringsOnly = FilterString<'a' | 1 | 'b' | 2>; // 'a' | 'b'

目标二:实现类型过滤与分发

1. 过滤联合类型

ts
type ExtractStrings<T> = T extends string ? T : never;
type T1 = ExtractStrings<'a' | 1 | 'b' | boolean>; // 'a' | 'b'

2. 分发式映射

ts
type ToArray<T> = T extends any ? T[] : never;
type T2 = ToArray<string | number>; // string[] | number[]

📌 实战:实现 ExtractExclude

ts
// 内置工具类型
type MyExtract<T, U> = T extends U ? T : never;
type MyExclude<T, U> = T extends U ? never : T;

type T3 = MyExtract<'a' | 'b' | 'c', 'a' | 'b'>; // 'a' | 'b'
type T4 = MyExclude<'a' | 'b' | 'c', 'a'>;       // 'b' | 'c'

目标三:使用 isasserts 编写自定义类型守卫

1. arg is Type(布尔式守卫)

ts
interface Admin {
  role: 'admin';
  permissions: string[];
}

interface User {
  role: 'user';
  department: string;
}

type Person = Admin | User;

function isAdmin(person: Person): person is Admin {
  return person.role === 'admin';
}

function manage(person: Person) {
  if (isAdmin(person)) {
    console.log(person.permissions); // ✅ 类型为 Admin
  }
}

2. asserts condition(断言式守卫)

更强大的方式:直接断言某个条件成立。

ts
function assertIsAdmin(person: Person): asserts person is Admin {
  if (person.role !== 'admin') {
    throw new Error('Not an admin');
  }
}

function manageStrict(person: Person) {
  assertIsAdmin(person);
  console.log(person.permissions); // ✅ 此后 person 一定是 Admin
}

🔍 asserts 守卫在函数执行后,整个作用域内都保证类型成立。


目标四:理解内置类型守卫

TS 能识别以下表达式作为类型守卫:

守卫作用示例
typeof v === "string"检查原始类型typeof x === 'number'
instanceof C检查类实例value instanceof Date
'key' in obj检查属性存在'bark' in animal
Array.isArray()检查数组Array.isArray(arr)
真值检查if (value) { ... }if (str) { str.trim() }

📌 真值守卫示例

ts
function printLength(text: string | null | undefined) {
  if (text) {
    console.log(text.length); // ✅ text: string(非 null/undefined)
  }
}

总结:本节核心要点

概念关键点
条件类型T extends U ? X : Y,实现类型“if”逻辑
分布式联合类型自动分发,可用于过滤
is 守卫arg is Type,用于函数返回值
asserts 守卫断言条件成立,影响后续所有代码
内置守卫typeof, instanceof, in, Array.isArray

📌 下一节我们将学习“映射类型进阶”与“模板字面量类型”,解锁更强大的类型编程能力。



练习题 1:实现条件类型

实现一个类型 GetReturnType<T>,如果 T 是函数,则返回其返回值类型,否则返回 never

ts
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type T1 = GetReturnType<() => string>; // string
type T2 = GetReturnType<string>;       // never

提示:使用 infer 推断返回值类型。


练习题 2:过滤联合类型

实现一个类型 NonNullable<T>,从联合类型中移除 nullundefined

ts
type NonNullable<T> = T extends null | undefined ? never : T;

type T3 = NonNullable<string \| number \| null \| undefined>; // string | number

对比内置的 NonNullable<T>


练习题 3:自定义类型守卫

为以下联合类型编写 in 操作符守卫:

ts
type Shape = 
  | { kind: 'circle'; radius: number }
  | { kind: 'square'; side: number };

function isCircle(shape: Shape): shape is Shape & { kind: 'circle' } {
  return shape.kind === 'circle';
}

// 测试
if (isCircle(shape)) {
  console.log(shape.radius); // ✅ 应该能访问 radius
}

练习题 4:asserts 守卫实战

实现一个函数 assertDefined,断言值不为 nullundefined

ts
function assertDefined<T>(value: T): asserts value is NonNullable<T> {
  if (value == null) {
    throw new Error('Value is null or undefined');
  }
}

// 测试
let maybeStr: string | null = localStorage.getItem('key');
assertDefined(maybeStr);
console.log(maybeStr.length); // ✅ 应该能访问 length

练习题 5:内置守卫应用

以下代码能否通过编译?为什么?

ts
function processValue(value: string | number | null) {
  if (typeof value === 'string' && value) {
    console.log(value.toUpperCase()); // ✅ 可以
  }
  if (value) {
    if (typeof value === 'string') {
      console.log(value.toUpperCase()); // ✅ 可以
    }
  }
}