前置知识: TypeScript

类型守卫与自定义守卫

00:00
4 min Intermediate 2026/6/14

类型守卫、自定义类型谓词

概述

类型守卫(Type Guard)是 TypeScript 在条件语句中收窄类型的机制。通过 typeof、instanceof、in 等内置运算符,TypeScript 能够在分支中自动推断更精确的类型。当内置守卫不够用时,可以通过自定义类型谓词(Type Predicate)和断言函数(Assertion Function)实现更复杂的类型收窄。类型守卫是编写类型安全代码的核心工具,掌握其使用方式对于处理联合类型和 unknown 类型的数据至关重要。

基础概念

typeof 守卫:使用 typeof 运算符检查原始类型(string、number、boolean、symbol、bigint、undefined、object、function)。适用于基本类型的区分。

instanceof 守卫:使用 instanceof 运算符检查实例类型。适用于类实例的区分,要求右侧是一个构造函数。

in 守卫:使用 in 运算符检查对象是否具有某个属性。适用于区分具有不同属性的对象类型。

自定义类型谓词:使用 parameterName is Type 作为函数返回值类型,告诉 TypeScript 在函数返回 true 时参数具有指定类型。

断言函数:使用 asserts parameterName is Typeasserts parameterName 作为函数返回值类型,在函数抛出异常时保证类型收窄。

快速上手

内置类型守卫

// typeof 守卫
function process(value: string | number) {
  if (typeof value === 'string') {
    // 此处 value 类型收窄为 string
    return value.toUpperCase();
  }
  // 此处 value 类型收窄为 number
  return value.toFixed(2);
}

// instanceof 守卫
function handleError(error: Error | string) {
  if (error instanceof TypeError) {
    // error 类型收窄为 TypeError
    console.log(error.message);
  }
}

// in 守卫
interface Fish {
  swim(): void;
}
interface Bird {
  fly(): void;
}

function move(animal: Fish | Bird) {
  if ('swim' in animal) {
    // animal 类型收窄为 Fish
    animal.swim();
  } else {
    // animal 类型收窄为 Bird
    animal.fly();
  }
}

自定义类型谓词

interface Dog {
  bark(): void;
  breed: string;
}
interface Cat {
  meow(): void;
  color: string;
}
type Pet = Dog | Cat;

// 自定义类型谓词:pet is Dog
function isDog(pet: Pet): pet is Dog {
  return 'bark' in pet;
}

function interact(pet: Pet) {
  if (isDog(pet)) {
    // pet 类型收窄为 Dog
    pet.bark();
    console.log(pet.breed);
  } else {
    // pet 类型收窄为 Cat
    pet.meow();
    console.log(pet.color);
  }
}

断言函数

// 断言值非空
function assertDefined<T>(
  value: T | undefined | null,
  message?: string
): asserts value is NonNullable<T> {
  if (value == null) {
    throw new Error(message ?? '值不能为空');
  }
}

// 使用
const maybeUser: User | undefined = getUser();
assertDefined(maybeUser, '用户不存在');
// 此处 maybeUser 类型收窄为 User

// 断言条件为真
function assert(condition: boolean, message?: string): asserts condition {
  if (!condition) {
    throw new Error(message ?? '断言失败');
  }
}

详细用法

通用守卫工具函数

// 字符串守卫
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

// 数字守卫(排除 NaN)
function isNumber(value: unknown): value is number {
  return typeof value === 'number' && !isNaN(value);
}

// 对象守卫(排除 null 和数组)
function isObject(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

// 数组守卫
function isArray<T>(value: unknown, itemGuard?: (item: unknown) => item is T): value is T[] {
  if (!Array.isArray(value)) return false;
  if (itemGuard) return value.every(itemGuard);
  return true;
}

// 使用
function processValue(value: unknown) {
  if (isString(value)) {
    console.log(value.toUpperCase()); // value: string
  } else if (isNumber(value)) {
    console.log(value.toFixed(2)); // value: number
  } else if (isObject(value)) {
    console.log(Object.keys(value)); // value: Record<string, unknown>
  }
}

嵌套类型守卫

// 验证 API 响应的结构
interface UserResponse {
  id: number;
  name: string;
  email: string;
  profile?: {
    avatar: string;
    bio: string;
  };
}

function isUserResponse(value: unknown): value is UserResponse {
  if (!isObject(value)) return false;
  return (
    isNumber(value.id) &&
    isString(value.name) &&
    isString(value.email) &&
    (value.profile === undefined ||
      (isObject(value.profile) && isString(value.profile.avatar) && isString(value.profile.bio)))
  );
}

// 使用
function handleResponse(data: unknown) {
  if (isUserResponse(data)) {
    console.log(data.name); // data: UserResponse
    if (data.profile) {
      console.log(data.profile.avatar); // data.profile: { avatar: string; bio: string }
    }
  }
}

可辨识联合与守卫

// 使用 kind 属性作为辨识标签
interface Circle {
  kind: 'circle';
  radius: number;
}

interface Rectangle {
  kind: 'rectangle';
  width: number;
  height: number;
}

interface Triangle {
  kind: 'triangle';
  base: number;
  height: number;
}

type Shape = Circle | Rectangle | Triangle;

// 使用 switch 进行类型收窄
function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      // shape 类型收窄为 Circle
      return Math.PI * shape.radius ** 2;
    case 'rectangle':
      // shape 类型收窄为 Rectangle
      return shape.width * shape.height;
    case 'triangle':
      // shape 类型收窄为 Triangle
      return 0.5 * shape.base * shape.height;
  }
}

常见场景

API 响应验证

// 验证未知数据是否符合预期类型
function validateApiResponse(data: unknown): User[] {
  if (!isArray(data)) {
    throw new Error('响应不是数组');
  }

  return data
    .filter((item): item is UserResponse => {
      return isUserResponse(item);
    })
    .map((item) => ({
      id: item.id,
      name: item.name,
      email: item.email,
    }));
}

表单数据验证

interface FormData {
  username: string;
  password: string;
  age: number;
  agree: boolean;
}

function isFormData(value: unknown): value is FormData {
  if (!isObject(value)) return false;
  return (
    isString(value.username) &&
    isString(value.password) &&
    isNumber(value.age) &&
    typeof value.agree === 'boolean'
  );
}

function submitForm(data: unknown) {
  if (!isFormData(data)) {
    throw new Error('表单数据格式错误');
  }
  // data 类型收窄为 FormData
  console.log(data.username, data.age);
}

Redux/状态管理 Action

// 使用类型守卫区分 Action 类型
interface IncrementAction {
  type: 'increment';
  payload: { amount: number };
}

interface DecrementAction {
  type: 'decrement';
  payload: { amount: number };
}

interface ResetAction {
  type: 'reset';
}

type CounterAction = IncrementAction | DecrementAction | ResetAction;

function isIncrementAction(action: CounterAction): action is IncrementAction {
  return action.type === 'increment';
}

function isDecrementAction(action: CounterAction): action is DecrementAction {
  return action.type === 'decrement';
}

function reducer(state: number, action: CounterAction): number {
  if (isIncrementAction(action)) {
    return state + action.payload.amount;
  }
  if (isDecrementAction(action)) {
    return state - action.payload.amount;
  }
  return 0; // reset
}

注意事项

  • 守卫函数的可靠性自定义类型谓词不会在运行时验证类型TypeScript 信任函数的返回值。如果守卫函数实现有误,会导致类型不安全。确保守卫逻辑与类型声明一致。
  • 窄化范围类型守卫只在当前作用域有效。将守卫结果赋值变量后,TypeScript 可能无法正确收窄类型,需要使用 const直接条件中使用。
  • 断言函数的异常断言函数在条件不满足时必须抛出异常,否则 TypeScript 无法正确收窄类型。不要在断言函数中使用 return 代替 throw
  • unknown vs any于来自外部的数据(API 响应用户输入等),使用 unknown 而非 any,然后通过类型守卫逐步收窄。这比 any安全

进阶用法

链式守卫

// 链式类型守卫
function isNonNull<T>(value: T | null | undefined): value is T {
  return value != null;
}

function isStringArray(value: unknown): value is string[] {
  return isArray(value, isString);
}

// 组合使用
function processItems(data: unknown) {
  if (isNonNull(data) && isStringArray(data)) {
    // data 类型收窄为 string[]
    data.forEach((item) => console.log(item.toUpperCase()));
  }
}

泛型守卫

// 泛型类型守卫
function hasProperty<K extends string>(value: unknown, key: K): value is Record<K, unknown> {
  return isObject(value) && key in value;
}

// 使用
function process(data: unknown) {
  if (hasProperty(data, 'name') && isString(data.name)) {
    console.log(data.name); // string
  }
}

// 类型安全的属性访问
function getPropertySafe<T, K extends keyof T>(obj: T, key: K): T[K] | undefined {
  return obj[key];
}

穷尽检查

// 使用 never 类型确保所有分支都被处理
type Result = 'success' | 'error' | 'pending';

function handleResult(result: Result) {
  switch (result) {
    case 'success':
      return '操作成功';
    case 'error':
      return '操作失败';
    case 'pending':
      return '处理中';
    default:
      // 如果遗漏了某个分支,result 的类型不是 never,编译报错
      const exhaustive: never = result;
      return exhaustive;
  }
}

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式