TypeScript 类型系统:接口、泛型与高级类型
TypeScript 类型系统:接口、泛型与高级类型
TypeScript 的核心优势在于其强大的类型系统,它能在编译阶段捕捉错误,提升代码的健壮性。本文将聚焦于类型系统中最重要的三个概念:接口、泛型与高级类型,帮助你从基础走向进阶。
1. 接口:定义数据的形状
接口(Interface)用于为对象、函数、类等定义“契约”,描述其应有的结构。通过接口,你可以确保代码中使用到的数据结构符合预期。
1.1 基本接口
使用 interface 关键字定义一个对象的形状,包含属性和其类型。
interface User {
name: string;
age: number;
}
function greet(user: User): string {
return `你好,${user.name},你今年 ${user.age} 岁。`;
}
const user: User = { name: "小明", age: 25 };
console.log(greet(user));
1.2 可选属性与只读属性
- 可选属性:在属性名后加
?,表示该属性可以不存在。 - 只读属性:使用
readonly修饰,初始化后不可修改。
interface Config {
readonly id: number;
url: string;
timeout?: number; // 可选属性
}
const config: Config = { id: 1, url: "https://api.example.com" };
// config.id = 2; // 错误!只读属性不可修改
1.3 函数类型接口
接口也可以描述函数类型:定义参数列表和返回值类型。
interface SearchFunc {
(source: string, subString: string): boolean;
}
const mySearch: SearchFunc = (src, sub) => {
return src.includes(sub);
};
1.4 接口继承
接口可以通过 extends 继承另一个接口,从而复用其成员,并可添加新成员。
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
bark(): void;
}
const myDog: Dog = {
name: "旺财",
breed: "金毛",
bark() {
console.log("汪汪!");
}
};
1.5 接口与类型别名的区别(简述)
接口和类型别名(type)很相似,但接口更擅长定义对象形状,具有更好的扩展性(可被继承和合并),而类型别名更灵活,能表示联合类型、交叉类型等。在大多数场景下,你可以根据个人/团队偏好选择,但复杂对象优先考虑接口。
2. 泛型:可复用的类型组件
泛型(Generics)允许你编写不预先指定具体类型的代码,而是在使用时再确定类型,从而实现类型安全的复用。
2.1 泛型函数
在函数名后使用 <T> 定义类型变量,T 可以用于参数、返回值,保持它们之间的关联。
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("hello"); // 显式指定类型
let output2 = identity(42); // 类型推断为 number
2.2 泛型接口与类
接口也能使用泛型,增强其灵活性。
interface Box<T> {
value: T;
}
const numberBox: Box<number> = { value: 10 };
const stringBox: Box<string> = { value: "类型安全" };
泛型类类似:
class Stack<T> {
private items: T[] = [];
push(item: T) { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
}
const numberStack = new Stack<number>();
numberStack.push(1);
2.3 泛型约束
使用 extends 限制泛型参数必须满足某种形状,例如必须拥有某个属性。
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("hello"); // 字符串有 length
logLength([1, 2, 3]); // 数组有 length
// logLength(10); // 错误!number 没有 length
2.4 在泛型中使用类型参数
一个类型参数可以约束另一个类型参数,常用于关联对象的属性。
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = { name: "小明", age: 20 };
getProperty(person, "name"); // 类型为 string
// getProperty(person, "address"); // 错误!"address" 不存在
3. 高级类型:构造更复杂的类型
随着应用复杂度的增加,你需要更灵活的类型构造手段。以下是一些常见的高级类型。
3.1 联合类型与交叉类型
- 联合类型(
|):值可以是多种类型之一。 - 交叉类型(
&):将多个类型合并为一个,拥有所有类型成员。
type ID = string | number; // 联合类型
let userId: ID = 1024; // 可以是数字
userId = "abc123"; // 也可以是字符串
interface Nameable {
name: string;
}
interface Ageable {
age: number;
}
type Person = Nameable & Ageable; // 交叉类型
const person: Person = { name: "小明", age: 25 };
3.2 类型别名与字面量类型
类型别名(type)可给任何类型起个新名字。字面量类型允许你使用具体的字符串或数字作为类型,常与联合类型结合使用。
type Direction = "north" | "south" | "east" | "west";
function move(direction: Direction) { /* ... */ }
move("north");
// move("up"); // 错误!只能使用指定的字面量
3.3 类型守卫与类型断言
- 类型守卫:运行时检查以在作用域内缩小类型范围,如
typeof、instanceof或自定义函数。 - 类型断言:你比编译器更清楚类型时,使用
as或尖括号语法告知类型。
function formatValue(value: string | number) {
if (typeof value === "string") {
return value.toUpperCase(); // 此处 value 被缩窄为 string
}
return value.toFixed(2); // 此处 value 为 number
}
// 类型断言
const someValue: unknown = "hello";
const strLength: number = (someValue as string).length;
3.4 条件类型与映射类型(简介)
- 条件类型:根据条件选择不同的类型,类似三元运算符。
T extends U ? X : Y - 映射类型:从已有类型创建新类型,对每个属性进行转换,常配合
keyof使用。
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"
// 映射类型:将所有属性变为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface Todo {
title: string;
description: string;
}
const todo: Readonly<Todo> = {
title: "学习 TypeScript",
description: "深入理解类型系统"
};
// todo.title = "修改"; // 错误,只读
总结
TypeScript 的类型系统通过接口定义形状,泛型实现可复用组件,高级类型提供灵活的构造能力,三者结合能够编写出更加健壮、可维护的代码。作为初学者,建议:
- 先用接口描述对象和函数的形状,熟悉可选/只读等基础特性。
- 在需要处理通用数据结构时引入泛型,渐进式扩展。
- 随着项目复杂化,逐步学习联合类型、字面量类型等高级模式。
掌握这些类型工具,你将能充分发挥 TypeScript 的威力。