返回首页

说说 TypeScript 中的类型推断和类型断言?

问题解析

类型推断和类型断言是 TypeScript 类型系统的两个重要特性,考察候选人对编译器类型推断机制的理解,以及何时需要手动进行类型断言。

核心概念

  • 类型推断(Type Inference):编译器自动推断变量类型
  • 类型断言(Type Assertion):手动告诉编译器变量的类型
  • 类型守卫(Type Guards):运行时类型检查,收窄类型范围
  • 非空断言! 标记值不为 null/undefined

详细解答

1. 类型推断

当类型没有给出时,TypeScript 编译器利用类型推断来推断类型。

1.1 基础类型推断

let str = "string";  // 推断为 string
let num = 123;       // 推断为 number
let bool = true;     // 推断为 boolean

1.2 上下文类型推断

// 根据上下文推断事件参数类型
window.onmousedown = function(event) {
  console.log(event.button);  // TypeScript 知道 event 是 MouseEvent
};

// 根据返回值推断
function createPerson() {
  return { name: "Tom", age: 25 };  // 推断为 { name: string; age: number; }
}

1.3 最佳通用类型推断

let arr = [0, 1, null];  // 推断为 (number | null)[]

let zoo = [new Rhino(), new Elephant(), new Snake()];
// 推断为 (Rhino | Elephant | Snake)[]
// 如果有共同基类,会推断为基类数组

1.4 函数返回值推断

function add(a: number, b: number) {
  return a + b;  // 推断返回值为 number
}

function greet(name: string) {
  return `Hello, ${name}`;  // 推断返回值为 string
}

1.5 泛型类型推断

function identity<T>(arg: T): T {
  return arg;
}

const num = identity(123);      // T 被推断为 number
const str = identity("hello");  // T 被推断为 string

2. 类型断言

类型断言好比是其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。

2.1 语法

TypeScript 提供了两种语法进行类型断言:

// 尖括号语法(在 JSX 中不能使用)
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

// as 语法(推荐,JSX 兼容)
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

2.2 应用场景

场景一:处理 any 类型

function getLength(something: string | number): number {
  if ((something as string).length) {
    return (something as string).length;
  } else {
    return something.toString().length;
  }
}

场景二:DOM 操作

const canvas = document.getElementById('canvas');
// canvas 类型是 HTMLElement | null

const canvas = document.getElementById('canvas') as HTMLCanvasElement;
// 现在可以使用 Canvas 特有的方法
canvas.getContext('2d');

场景三:处理联合类型

interface Cat {
  name: string;
  run(): void;
}

interface Fish {
  name: string;
  swim(): void;
}

function getPet(): Cat | Fish {
  // ...
}

const pet = getPet();
(pet as Fish).swim();  // 断言为 Fish 类型

2.3 双重断言

类型断言的层次可以连续:

// 先断言为 any,再断言为目标类型
const str: string = 'hello';
const num = (str as any) as number;  // 不推荐,破坏了类型安全

警告:双重断言可能会破坏类型安全,应尽量避免。

3. 非空断言

在变量名后添加 ! 表示该值不为 null 或 undefined:

function processEntity(e?: Entity) {
  let s = e!.name;  // 断言 e 不为 undefined
}

// 与可选链操作符的区别
const name = entity?.name;  // 如果 entity 为 null/undefined,返回 undefined
const name = entity!.name;  // 断言 entity 一定存在,直接访问 name

4. 确定赋值断言

TypeScript 4.0+ 引入,允许在声明变量时断言该变量会被赋值:

let x!: number;  // 确定赋值断言
initialize();
console.log(x + x);  // 不会报错

function initialize() {
  x = 10;
}

5. const 断言

TypeScript 3.4+ 引入,将表达式标记为不可变:

// 普通推断
let x = 'hello' as 'hello';  // 类型为 'hello'

// const 断言
let y = 'hello' as const;    // 类型为 'hello'

// 对象字面量
let obj = {
  x: 10,
  y: [20, 30]
} as const;
// obj.x 类型为 10(不是 number)
// obj.y 类型为 readonly [20, 30]

深入理解

类型推断的局限性

// 推断过于宽泛
const config = {
  host: 'localhost',
  port: 3000
};  // 推断为 { host: string; port: number; }

// 使用 as const 获得更精确的类型
const config = {
  host: 'localhost',
  port: 3000
} as const;  // 推断为 { readonly host: 'localhost'; readonly port: 3000; }

// 或者显式注解
const config: { host: string; port: number } = {
  host: 'localhost',
  port: 3000
};

类型断言的安全使用

// 不安全的断言
const input = document.getElementById('input') as HTMLInputElement;
// 如果元素不存在,运行时报错

// 安全的断言
const input = document.getElementById('input');
if (input) {
  (input as HTMLInputElement).value = 'hello';
}

// 或者使用类型守卫
function isInputElement(el: HTMLElement | null): el is HTMLInputElement {
  return el !== null && el.tagName === 'INPUT';
}

if (isInputElement(input)) {
  input.value = 'hello';  // 类型已收窄
}

最佳实践

  1. 优先依赖类型推断:减少不必要的类型注解,让代码更简洁
  2. 谨慎使用类型断言
    • 只在必要时使用
    • 确保断言的正确性
    • 避免双重断言
  3. 使用类型守卫替代断言:运行时检查比编译时断言更安全
  4. 非空断言要谨慎:确保值确实不为 null/undefined
  5. as const 用于常量:获得更精确的字面量类型

面试要点

  1. 类型推断:编译器自动推断变量类型,无需显式注解
  2. 推断场景
    • 变量初始化
    • 函数返回值
    • 泛型参数
    • 上下文类型
  3. 类型断言:手动指定类型,两种语法 <Type>as Type
  4. 应用场景
    • 处理 any 类型
    • DOM 元素类型
    • 联合类型收窄
  5. 非空断言! 标记值不为 null/undefined
  6. const 断言as const 创建只读字面量类型
  7. 安全性:类型断言不进行运行时检查,需谨慎使用
  8. 与类型守卫的区别:断言是编译时行为,守卫是运行时检查