← 返回导航

类型收窄的 5 种方式

进阶技巧 · 基础

场景说明

类型收窄(Type Narrowing)是 TypeScript 将宽泛类型缩小为更具体类型的过程。比如一个参数是 string | number,在某个分支中 TS 推断出它就是 string。掌握这 5 种收窄方式,可以让你写出更安全、更简洁的类型代码。

完整代码


      
// 类型收窄的 5 种方式

// 方式一:typeof 类型收窄
function padLeft(value: string | number, padding: string | number) {
  if (typeof padding === 'number') {
    // padding 收窄为 number
    return Array(padding + 1).join(' ') + value;
  }
  if (typeof padding === 'string') {
    // padding 收窄为 string
    return padding + value;
  }
  throw new Error('Invalid padding');
}

// 方式二:instanceof 类型收窄
class ApiError extends Error {
  constructor(public code: number, message: string) {
    super(message);
  }
}

function handleError(err: unknown) {
  if (err instanceof ApiError) {
    // err 收窄为 ApiError
    console.log(`错误码: ${err.code}, 消息: ${err.message}`);
  } else if (err instanceof Error) {
    // err 收窄为 Error
    console.log(err.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();
  }
}

// 方式四:真值判断收窄(排除 null/undefined)
function printAll(strs: string | string[] | null) {
  if (strs && typeof strs === 'object') {
    // strs 收窄为 string[](排除了 null 和 string)
    strs.forEach(s => console.log(s));
  } else if (typeof strs === 'string') {
    // strs 收窄为 string
    console.log(strs);
  }
}

// 方式五:断言函数收窄(asserts 关键字)
function assertIsString(value: unknown): asserts value is string {
  if (typeof value !== 'string') {
    throw new Error('Not a string!');
  }
}

function processValue(value: unknown) {
  assertIsString(value);
  // 断言函数执行后,value 被收窄为 string
  console.log(value.toUpperCase());
}

关键点解析

使用示例与类型推导


      
// 实战:处理 API 请求的多种状态

type RequestState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };

function render<T>(state: RequestState<T>) {
  switch (state.status) {
    case 'idle':
      return '请发起请求';
    case 'loading':
      return '加载中...';
    case 'success':
      // state 被收窄为 { status: 'success'; data: T }
      return JSON.stringify(state.data);
    case 'error':
      // state 被收窄为 { status: 'error'; error: Error }
      return state.error.message;
    default:
      // never 检查:确保穷尽了所有可能
      const _exhaustive: never = state;
      return _exhaustive;
  }
}
在 Playground 中尝试