Skip to content
第 100 / 250 章前端⏱ 10 分钟阅读

第 100 章:类型断言与类型守卫

学习目标

  • 掌握类型断言的两种语法
  • 理解类型守卫的常见收窄方式
  • 学会自定义类型谓词
  • 区分断言与守卫的使用边界

一、类型断言(Type Assertion)

类型断言告诉 TS:「我比你更确定这个值的类型,照我说做」。

1.1 两种语法

typescript
// 写法 1:as 语法(推荐)
const value = someValue as string;

// 写法 2:尖括号(不推荐,与 JSX 冲突)
const value = <string>someValue;

1.2 常见使用场景

typescript
// 场景 1:DOM 操作
const input = document.querySelector('#username') as HTMLInputElement;
input.value = 'Tom';

// 场景 2:JSON 解析
interface User { id: number; name: string }
const json = '{"id":1,"name":"Tom"}';
const user = JSON.parse(json) as User;

// 场景 3:联合类型收窄
function getLength(value: string | number): number {
  if ((value as string).length !== undefined) {
    return (value as string).length;
  }
  return value.toString().length;
}

1.3 双重断言(最后的逃生舱)

typescript
// 当两个类型完全无关时,需要双重断言
const value = 'hello' as unknown as number;  // ⚠️ 慎用

二、类型断言的陷阱

2.1 错误示例

typescript
// ❌ 断言为不兼容的类型
const num = 123 as string;  // ❌ Conversion of type 'number' to type 'string' may be a mistake

// ❌ 过度断言导致运行时错误
const value: string | number = 'hello';
const len = (value as string).length;  // ✅ 编译通过
// 但如果运行时 value 是 number,这里就会出错

2.2 const 断言

typescript
// 让字面量变成"字面量类型"
const x = 'hello';        // 类型: string
const y = 'hello' as const;  // 类型: 'hello'

// 数组
const arr = [1, 2, 3] as const;  // 类型: readonly [1, 2, 3]

// 对象
const config = {
  api: '/api',
  method: 'GET'
} as const;
// 类型: { readonly api: '/api'; readonly method: 'GET' }

三、类型守卫(Type Guard)

类型守卫是真正安全的类型收窄方式。

3.1 typeof 守卫

typescript
function padLeft(value: string, padding: string | number): string {
  if (typeof padding === 'number') {
    return ' '.repeat(padding) + value;  // ✅ padding 是 number
  }
  return padding + value;  // ✅ padding 是 string
}

typeof 可识别的类型:

typescript
typeof x === 'string'      // ✅
typeof x === 'number'      // ✅
typeof x === 'bigint'      // ✅
typeof x === 'boolean'     // ✅
typeof x === 'symbol'      // ✅
typeof x === 'undefined'   // ✅
typeof x === 'object'      // ⚠️ null 也是 'object'
typeof x === 'function'    // ✅

3.2 真值守卫

typescript
function printName(name: string | null | undefined) {
  if (name) {
    console.log(name.toUpperCase());  // ✅ 收窄为 string
  }
}

// 简化:!
// const actualName = name!;

3.3 相等守卫

typescript
function compare(x: string | number, y: string | number) {
  if (x === y) {
    x.toUpperCase();  // ✅ 收窄为 string(因为 === 只能是同类型)
  }
}

3.4 in 守卫

typescript
interface Fish { swim(): void; }
interface Bird { fly(): void; }

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

3.5 instanceof 守卫

typescript
function logDate(date: string | Date) {
  if (date instanceof Date) {
    console.log(date.toISOString());  // ✅ Date
  } else {
    console.log(date.length);         // ✅ string
  }
}

3.6 赋值守卫

typescript
function process(value: string | number) {
  let result: string;
  if (typeof value === 'string') {
    result = value;  // ✅
  } else {
    result = String(value);  // ✅
  }
  // 这里 result 是 string
}

四、类型谓词(Type Predicate)

自定义类型守卫函数,让 TS 知道某个函数能收窄类型。

4.1 语法

typescript
function 函数名(参数: 宽类型): 参数 is 窄类型 {
  return boolean;
}

4.2 基础示例

typescript
interface Cat { meow(): void; kind: 'cat' }
interface Dog { bark(): void; kind: 'dog' }

function isCat(animal: Cat | Dog): animal is Cat {
  return animal.kind === 'cat';
}

function describe(animal: Cat | Dog) {
  if (isCat(animal)) {
    animal.meow();  // ✅ Cat
  } else {
    animal.bark();  // ✅ Dog
  }
}

4.3 常见工具类型谓词

typescript
// 非空检查
function isNotNull<T>(value: T | null): value is T {
  return value !== null;
}

// 数组检查
function isArray<T>(value: T | T[]): value is T[] {
  return Array.isArray(value);
}

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

4.4 复杂谓词

typescript
type ApiResult<T> =
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };

function isSuccess<T>(result: ApiResult<T>): result is { status: 'success'; data: T } {
  return result.status === 'success';
}

function handle<T>(result: ApiResult<T>) {
  if (isSuccess(result)) {
    console.log(result.data);  // ✅ T
  } else {
    console.error(result.error);  // ✅ Error
  }
}

五、断言守卫(Assertion Function)

断言函数抛出错误改变类型

5.1 语法

typescript
function 函数名(参数): asserts 参数 is 类型 {
  // 抛错或不做任何事
}

5.2 基础示例

typescript
function assertString(value: unknown): asserts value is string {
  if (typeof value !== 'string') {
    throw new Error('必须是字符串');
  }
}

function process(value: unknown) {
  assertString(value);
  value.toUpperCase();  // ✅ 断言后是 string
}

5.3 应用:API 参数校验

typescript
function assertUser(obj: unknown): asserts obj is User {
  if (!obj || typeof obj !== 'object') {
    throw new Error('不是对象');
  }
  const u = obj as User;
  if (typeof u.id !== 'number' || typeof u.name !== 'string') {
    throw new Error('字段类型错误');
  }
}

function handle(apiResponse: unknown) {
  assertUser(apiResponse);
  console.log(apiResponse.name);  // ✅ string
}

六、断言 vs 守卫对比

特性类型断言类型守卫
语法value as Typetypeof x === '...'
运行时检查❌ 不检查✅ 检查
安全性⚠️ 可能骗 TS✅ 安全
何时用「我确定是这个类型」「不确定,需要先判断」
typescript
// ❌ 用断言(不安全)
const value = getValue() as string;
value.toUpperCase();  // 运行时可能挂

// ✅ 用守卫(安全)
const value = getValue();
if (typeof value === 'string') {
  value.toUpperCase();  // ✅ 一定安全
}

七、常见错误

7.1 断言改写类型

typescript
// ❌ 你以为的转换
const x = '123' as unknown as number;  // 编译过,运行挂
x.toFixed(2);  // ❌ TypeError: x.toFixed is not a function

// ✅ 真正的转换
const x = Number('123');  // 123
x.toFixed(2);  // ✅ "123.00"

7.2 断言联合类型

typescript
// ❌ 编译过,运行炸
const value = getValue() as string;
if (typeof value === 'number') {
  // 这里进不来因为断言成 string 了,逻辑漏洞
}

7.3 滥用 as any

typescript
// ❌ 完全放弃类型
const value = something as any;
value.foo.bar.baz.qux;  // 编译过,运行不知道

// ✅ 用 unknown 代替
const value: unknown = something;
if (typeof value === 'object' && value !== null) {
  // 收窄后安全使用
}

八、实战案例

8.1 表单输入校验

typescript
function validate(input: unknown): string {
  if (typeof input !== 'string') {
    throw new Error('必须是字符串');
  }
  if (input.length < 3) {
    throw new Error('至少 3 个字符');
  }
  return input.trim();
}

const formValue = form.get('username') as string;
try {
  const cleanValue = validate(formValue);
  // ...
} catch (e) {
  // 显示错误
}

8.2 API 响应处理

typescript
interface UserResponse {
  code: number;
  data: User;
}

interface ApiError {
  code: number;
  message: string;
}

function isUserResponse(r: UserResponse | ApiError): r is UserResponse {
  return 'data' in r;
}

async function fetchUser(id: number): Promise<User> {
  const res: UserResponse | ApiError = await fetch(`/api/users/${id}`).then(r => r.json());

  if (!isUserResponse(res)) {
    throw new Error(res.message);
  }

  return res.data;
}

8.3 状态库中的守卫

typescript
type State =
  | { kind: 'idle' }
  | { kind: 'loading' }
  | { kind: 'success'; data: User }
  | { kind: 'error'; error: Error };

function isIdle(s: State): s is { kind: 'idle' } {
  return s.kind === 'idle';
}

function isSuccess(s: State): s is { kind: 'success'; data: User } {
  return s.kind === 'success';
}

// 使用
if (isSuccess(state)) {
  console.log(state.data.name);  // ✅
}

九、本章小结

要点关键
类型断言value as Type,告诉 TS 你比它确定
类型守卫typeof / in / instanceof 收窄类型
类型谓词param is Type,自定义守卫函数
断言守卫asserts param is Type,失败抛错
最佳实践优先用守卫,断言是最后手段

动手练习

  1. 类型守卫:写一个 isNonEmptyArray<T>(value: T[]): value is [T, ...T[]] 守卫
  2. API 响应:定义 Success<T>Failure 类型,写一个 isSuccess 守卫
  3. 断言函数:写一个 assertPositive(n: number): asserts n is number 函数

推荐阅读


下一章第 101 章:泛型(Generics)

本站基于 VitePress 构建 · 由 Codebook 团队维护