类型断言(Type Assertion)
概述
类型断言是一种告诉 TypeScript 编译器"我知道这个值的类型比 TypeScript 推断的更具体"的方式。
基本语法
尖括号语法
// 尖括号语法
let someValue: any = 'hello';
let strLength: number = (<string>someValue).length;
// 在 JSX 中不能使用尖括号语法
// let strLength = (<string>someValue).length; // JSX 中会报错
as 语法
// as 语法(推荐)
let someValue: any = 'hello';
let strLength: number = (someValue as string).length;
// 在 JSX 中使用 as 语法
let strLength2 = (someValue as string).length;
常见使用场景
1. 将 any 转换为具体类型
// 从 API 获取数据
const response: any = await fetch('/api/user');
const user = response as User;
// DOM 操作
const element = document.getElementById('myElement') as HTMLInputElement;
console.log(element.value);
2. 将联合类型转换为具体类型
// 联合类型断言
function formatValue(value: string | number): string {
if (typeof value === 'string') {
return (value as string).toUpperCase();
}
return (value as number).toFixed(2);
}
3. 将父类转换为子类
class Animal {
name: string;
}
class Dog extends Animal {
bark(): void {
console.log('Woof!');
}
}
const animal: Animal = new Dog();
const dog = animal as Dog;
dog.bark();
非空断言
基本用法
// 非空断言操作符 !
function getLength(str: string | null | undefined): number {
// 告诉 TypeScript str 不是 null 或 undefined
return str!.length;
}
// DOM 操作
const element = document.getElementById('myElement')!;
console.log(element.innerHTML);
谨慎使用
// 可能导致运行时错误
function badExample(str: string | null): number {
return str!.length; // 如果 str 是 null,会抛出错误
}
// 更安全的方式
function goodExample(str: string | null): number {
return str?.length ?? 0; // 使用可选链和空值合并
}
const 断言
基本用法
// const 断言
let str = 'hello' as const; // 类型为 'hello',而不是 string
let num = 42 as const; // 类型为 42,而不是 number
let bool = true as const; // 类型为 true,而不是 boolean
// 数组 const 断言
let arr = [1, 2, 3] as const; // 类型为 readonly [1, 2, 3]
// arr.push(4); // 错误:只读数组
// 对象 const 断言
let obj = { name: 'John', age: 25 } as const;
// obj.name = 'Jane'; // 错误:只读属性
实际应用
// 配置对象
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
} as const;
// 状态类型
const Status = {
Active: 'ACTIVE',
Inactive: 'INACTIVE',
Pending: 'PENDING'
} as const;
type Status = typeof Status[keyof typeof Status];
// 'ACTIVE' | 'INACTIVE' | 'PENDING'
类型守卫
typeof 类型守卫
function processValue(value: string | number): string {
if (typeof value === 'string') {
// 此处 TypeScript 知道 value 是 string 类型
return value.toUpperCase();
}
// 此处 TypeScript 知道 value 是 number 类型
return value.toFixed(2);
}
instanceof 类型守卫
class Dog {
bark(): void {
console.log('Woof!');
}
}
class Cat {
meow(): void {
console.log('Meow!');
}
}
function makeSound(animal: Dog | Cat): void {
if (animal instanceof Dog) {
animal.bark(); // TypeScript 知道 animal 是 Dog
} else {
animal.meow(); // TypeScript 知道 animal 是 Cat
}
}
in 类型守卫
interface Fish {
swim: () => void;
}
interface Bird {
fly: () => void;
}
function move(animal: Fish | Bird): void {
if ('swim' in animal) {
animal.swim(); // TypeScript 知道 animal 是 Fish
} else {
animal.fly(); // TypeScript 知道 animal 是 Bird
}
}
自定义类型守卫
// 自定义类型守卫函数
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
function isCat(animal: Cat | Dog): animal is Cat {
return (animal as Cat).meow !== undefined;
}
function makeSound(animal: Cat | Dog): void {
if (isCat(animal)) {
animal.meow(); // TypeScript 知道 animal 是 Cat
} else {
animal.bark(); // TypeScript 知道 animal 是 Dog
}
}
实际应用示例
1. DOM 操作
// 获取元素
const input = document.getElementById('username') as HTMLInputElement;
const button = document.querySelector('.submit-btn') as HTMLButtonElement;
// 类型守卫
function handleElement(element: HTMLElement): void {
if (element instanceof HTMLInputElement) {
console.log('Input value:', element.value);
} else if (element instanceof HTMLButtonElement) {
console.log('Button text:', element.textContent);
}
}
2. API 响应处理
interface User {
id: number;
name: string;
email: string;
}
interface ApiResponse<T> {
data: T;
status: number;
}
async function fetchUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const data: ApiResponse<any> = await response.json();
// 类型断言
return data.data as User;
}
// 类型守卫
function isUser(obj: any): obj is User {
return (
typeof obj === 'object' &&
typeof obj.id === 'number' &&
typeof obj.name === 'string' &&
typeof obj.email === 'string'
);
}
3. 配置处理
interface Config {
apiUrl: string;
timeout: number;
debug: boolean;
}
function loadConfig(): Config {
const rawConfig = JSON.parse(localStorage.getItem('config') || '{}');
// 类型守卫验证
if (
typeof rawConfig.apiUrl === 'string' &&
typeof rawConfig.timeout === 'number' &&
typeof rawConfig.debug === 'boolean'
) {
return rawConfig as Config;
}
throw new Error('Invalid config format');
}
注意事项
1. 避免过度使用类型断言
// 不推荐
const value: any = 'hello';
const length: number = (value as string).length; // 不安全
// 推荐
const value: string | number = 'hello';
if (typeof value === 'string') {
const length: number = value.length; // 安全
}
2. 使用类型守卫代替类型断言
// 不推荐
function process(value: string | number) {
const str = value as string; // 不安全
console.log(str.toUpperCase());
}
// 推荐
function process(value: string | number) {
if (typeof value === 'string') {
console.log(value.toUpperCase());
}
}
3. 使用 const 断言保护常量
// 不推荐
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000
};
config.apiUrl = 'https://other.com'; // 可以修改
// 推荐
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000
} as const;
// config.apiUrl = 'https://other.com'; // 错误:只读
最佳实践
- 优先使用类型守卫:更安全
- 使用 as 语法:避免 JSX 冲突
- 使用 const 断言:保护常量
- 避免不必要的类型断言:让 TypeScript 推断
// 好的做法
function process(value: string | number): string {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value.toFixed(2);
}
// 避免
function process(value: string | number): string {
return (value as string).toUpperCase(); // 可能运行时错误
}
参考
目录