TypeScript 高级类型使用指南
TypeScript 的类型系统非常强大,掌握高级类型能够显著提升代码的健壮性和开发体验。本文将深入探讨 TypeScript 的高级类型,并通过实例讲解它们的应用场景。
基础类型回顾
在深入高级类型之前,让我们先回顾一下 TypeScript 的基础类型:
// 基本类型
const isActive: boolean = true;
const count: number = 42;
const username: string = 'Charon';
// 数组
const numbers: number[] = [1, 2, 3];
const names: Array<string> = ['Alice', 'Bob'];
// 元组
const point: [number, number] = [10, 20];
// 枚举
enum Direction {
Up,
Down,
Left,
Right
}
const move: Direction = Direction.Up;
// Any, Unknown, Void, Null, Undefined
const anything: any = 'could be anything';
const something: unknown = 'type-safe any';
const noReturn: void = undefined;
const empty: null = null;
const notAssigned: undefined = undefined;
联合类型与交叉类型
联合类型和交叉类型是 TypeScript 中最常用的高级类型组合方式。
联合类型(Union Types)
联合类型表示一个值可以是几种类型之一:
// 联合类型示例
type ID = string | number;
function printId(id: ID) {
console.log(id);
// 类型收窄
if (typeof id === 'string') {
console.log(id.toUpperCase());
} else {
console.log(id.toFixed(2));
}
}
printId("abc123"); // 可以是字符串
printId(123456); // 也可以是数字
交叉类型(Intersection Types)
交叉类型将多个类型合并为一个类型,包含了所需的所有类型的特性:
// 定义两个接口
interface HasName {
name: string;
}
interface HasAge {
age: number;
}
// 使用交叉类型组合接口
type Person = HasName & HasAge;
// 必须同时包含两个接口的所有属性
const person: Person = {
name: "Alice",
age: 30
};
类型别名与接口
TypeScript 提供了接口和类型别名两种方式来定义对象类型,它们有细微的差别。
类型别名(Type Aliases)
类型别名用 type
关键字定义,可以为任何类型提供新名称:
// 简单的类型别名
type Point = {
x: number;
y: number;
};
// 可以引用自身(递归类型)
type Tree<T> = {
value: T;
children?: Tree<T>[];
};
// 联合类型的别名
type Status = "pending" | "fulfilled" | "rejected";
const result: Status = "fulfilled";
接口(Interfaces)
接口用 interface
关键字定义,用于描述对象的形状:
// 基本接口
interface User {
id: number;
name: string;
}
// 扩展接口
interface Employee extends User {
department: string;
}
const employee: Employee = {
id: 1,
name: "John",
department: "Engineering"
};
// 声明合并(接口的独特特性)
interface Window {
title: string;
}
interface Window {
ts: TypeScriptAPI;
}
// 两个接口声明会合并
const src: Window = { title: "TypeScript", ts: {} };
条件类型
条件类型是 TypeScript 中极其强大的特性,它使类型系统具备了逻辑判断能力。
// 基本条件类型语法:T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type Result1 = IsString<"hello">; // true
type Result2 = IsString<42>; // false
// 条件类型中的推断(infer)
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function greet(): string {
return "Hello!";
}
type GreetReturn = ReturnType<typeof greet>; // string
映射类型
映射类型可以从现有类型创建新类型,通过遍历现有类型的属性来转换或修改它们。
// 基本映射类型
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface Todo {
title: string;
description: string;
}
// 创建一个所有属性都为只读的新类型
const todo: Readonly<Todo> = {
title: "Learn TypeScript",
description: "Study advanced types"
};
// todo.title = "New Title"; // Error: Cannot assign to 'title' because it is a read-only property
// Partial 映射类型,使所有属性可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 部分更新对象
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
const updatedTodo = updateTodo(todo, {
description: "Focus on mapped types"
});
工具类型
TypeScript 内置了许多实用的工具类型,它们都是通过类型操作而非运行时操作来实现的。
// 内置工具类型示例
interface Person {
name: string;
age: number;
address: {
city: string;
country: string;
};
}
// Pick:从类型中选择部分属性
type PersonName = Pick<Person, 'name'>; // { name: string }
// Omit:排除某些属性
type PersonWithoutAddress = Omit<Person, 'address'>; // { name: string; age: number }
// Exclude:从联合类型中排除某些类型
type Primitive = string | number | boolean;
type StringOrNumber = Exclude<Primitive, boolean>; // string | number
// Extract:提取符合条件的类型
type StringOnly = Extract<Primitive, string>; // string
// Required:使所有属性必选
type RequiredPerson = Required<Partial<Person>>; // 与 Person 相同
// Record:创建具有特定类型属性的类型
type CountryInfo = Record<string, { population: number }>;
const countries: CountryInfo = {
China: { population: 1400000000 },
US: { population: 330000000 }
};
实际应用案例
API 响应类型
使用条件类型和泛型定义 API 响应类型:
// API响应类型
interface ApiResponse<T> {
data: T;
status: number;
message: string;
timestamp: string;
}
// 不同实体类型
interface User {
id: number;
username: string;
}
interface Post {
id: number;
title: string;
content: string;
}
// 使用泛型创建特定响应类型
type UserResponse = ApiResponse<User>;
type PostResponse = ApiResponse<Post>;
// 模拟API调用
async function fetchUser(id: number): Promise<UserResponse> {
// 实际应用中这里会调用API
return {
data: { id, username: "user" + id },
status: 200,
message: "Success",
timestamp: new Date().toISOString()
};
}
React 组件 Props 类型
在 React 项目中使用 TypeScript 定义组件 Props:
// 基础属性
interface BaseProps {
className?: string;
style?: React.CSSProperties;
}
// 按钮属性
interface ButtonProps extends BaseProps {
type: 'primary' | 'secondary' | 'danger';
size?: 'small' | 'medium' | 'large';
onClick: () => void;
disabled?: boolean;
children: React.ReactNode;
}
// 定义组件
function Button({
type,
size = 'medium',
onClick,
disabled,
children,
...rest
}: ButtonProps) {
return (
<button
className={`btn btn-${type} btn-${size}`}
onClick={onClick}
disabled={disabled}
{...rest}
>
{children}
</button>
);
}
高级类型编程
TypeScript 的类型系统本身就是一种编程语言,可以用来编写复杂的类型。
递归类型
定义具有无限嵌套能力的类型:
// 递归定义JSON值类型
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
// 定义嵌套对象
const data: JSONValue = {
name: "TypeScript",
version: 4.5,
features: ["templates", "types"],
metadata: {
releaseDate: "2021-11-17",
isStable: true,
supporters: [
{ name: "Microsoft", level: 1 }
]
}
};
字符串操作类型
使用模板字面类型操作字符串:
// 字符串操作
type Greeting = `Hello, ${string}!`;
// 有效的值
const g1: Greeting = "Hello, World!";
const g2: Greeting = "Hello, TypeScript!";
// 无效的值
// const g3: Greeting = "Hi, World!"; // Error
// 模板字面类型结合联合类型
type EventName = 'click' | 'focus' | 'blur';
type EventHandler = `on${Capitalize<EventName>}`; // 'onClick' | 'onFocus' | 'onBlur'
// 路径生成类型
type NestedPaths<T, P extends string = ''> =
T extends object
? { [K in keyof T]: K extends string
? P extends ''
? NestedPaths<T[K], K>
: NestedPaths<T[K], `${P}.${K}`>
: never
}[keyof T] | P
: P;
// 生成所有可能的嵌套对象路径
interface User {
name: string;
address: {
street: string;
city: string;
geo: {
lat: number;
lng: number;
}
}
}
type UserPaths = NestedPaths<User>;
// 'name' | 'address' | 'address.street' | 'address.city' | 'address.geo' | 'address.geo.lat' | 'address.geo.lng'
类型安全的实战技巧
类型守卫
使用类型守卫进行运行时类型检查:
// 用户定义的类型守卫
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function processValue(value: unknown) {
if (isString(value)) {
// 在这个作用域中,TypeScript 知道 value 是字符串
console.log(value.toUpperCase());
} else {
console.log("Not a string:", value);
}
}
// instanceof 类型守卫
class ApiError extends Error {
statusCode: number = 500;
}
class ValidationError extends Error {
validationErrors: string[] = [];
}
function handleError(error: Error) {
if (error instanceof ApiError) {
// 处理API错误
console.error(`API Error ${error.statusCode}: ${error.message}`);
} else if (error instanceof ValidationError) {
// 处理验证错误
console.error(`Validation Errors: ${error.validationErrors.join(', ')}`);
} else {
// 处理其他错误
console.error(`Unknown Error: ${error.message}`);
}
}
严格的空值检查
TypeScript 的严格空检查可以避免常见的空引用错误:
// 开启严格空检查时,可选参数和可选属性类型会自动包含 undefined
function printName(name?: string) {
// 错误: 'name' 可能为 undefined
// console.log(name.toUpperCase());
// 正确: 先检查是否存在
if (name) {
console.log(name.toUpperCase());
} else {
console.log("No name provided");
}
}
// 非空断言操作符(当你确定某个值不可能为空时使用)
function getNonNullName(name: string | null) {
// 使用 ! 告诉 TypeScript 该值不为 null 或 undefined
return name!.toUpperCase();
}
结论
TypeScript 的高级类型系统使我们能够构建更加健壮的应用程序,通过类型定义捕获潜在错误。掌握这些高级类型特性,你将能够:
- 创建更精确的类型定义,提高代码质量
- 减少运行时错误,增强代码稳定性
- 提供更好的开发体验和自动补全功能
- 使重构更加安全可靠
随着对 TypeScript 类型系统的深入理解,你会发现类型本身就是一种强大的抽象和文档工具,它可以清晰地表达代码的意图和约束,使团队合作更加高效。
在下一篇文章中,我们将探讨如何进行更复杂的 TypeScript 类型体操练习,挑战你的类型系统理解能力!