TypeScript 泛型与类型系统:从入门到精通
TypeScript 作为 JavaScript 的类型超集,为我们提供了强大的类型系统。今天,就让我们一起探索 TS 中的泛型、type 和 interface,解锁类型安全的新姿势!🚀
前言:为什么需要 TypeScript?
JavaScript 作为一门弱类型语言,开发时非常灵活,但这种灵活性也带来了很多问题:
javascript
// JavaScript 弱类型,容易出问题
let num = 1;
num = 'hello'; // 这在 JS 中是合法的,但可能导致后续代码出错
num.toFixed(2); // 运行时才会报错:Uncaught TypeError
TypeScript 的出现正是为了解决这些问题!它为我们带来了类型的约束,让错误在编译阶段就能被发现。
有趣的是,TypeScript 的诞生还有一个背景:微软想让 Java 工程师更容易地编写前端代码。如今,React + TypeScript 已经成为前端开发的标配,掌握 TS 是每个前端开发者的必备技能!
ts与js对比
特性 | JavaScript | TypeScript |
---|---|---|
类型检查 | 运行时(动态) | 编译时(静态)✅ |
错误发现 | 运行时才暴露 | 编写时即可发现✅ |
IDE 智能提示 | 基础 | 强大、精准✅ |
代码可读性 | 依赖注释和命名 | 类型即文档✅ |
大型项目维护 | 易出错、难重构 | 更安全、易维护✅ |
学习成本 | 低 | 稍高(需学类型系统) |
构建步骤 | 无 | 需编译(tsc) |
第一部分:泛型 - 类型的函数
1.1 什么是泛型?
泛型是类型的函数,T 是它的占位符,接受类型参数并返回新类型。泛型在类型层面引入了参数化机制,核心目标是在编译期间提供类型安全,同时保持代码的复用性。
1.2 解决 any 滥用问题
很多 TS 新手会滥用 any
类型:
typescript
let a: any = 1; // any 表示可以是任何类型
a = '12'; // 不能滥用any,这失去了TS的意义
function getFirstElement(arr: any[]): any {
return arr[0];
}
const numbers = [1, 2, 3, 4];
const firstNum = getFirstElement(numbers);
console.log(firstNum); // any类型
firstNum.toFixed(2); // 运行时可能报错!
const strs = ['1', '2', '3'];
const firstStr = getFirstElement(strs);
console.log(firstStr); // any类型
这种情况下,我们失去了 TypeScript 的类型检查优势。泛型正是解决这个问题的利器!
1.3 泛型的基本使用
typescript
// 使用泛型来提高函数的复用性
function getFirstElement2<T>(arr: T[]): T | undefined {
return arr.length ? arr[0] : undefined;
}
// 显式指定类型参数
const firstNum2 = getFirstElement2<number>(numbers);
firstNum2?.toFixed(2); // 安全的使用方式
console.log(firstNum2); // number类型
// TypeScript 类型推导,自动推断类型
const firstStr2 = getFirstElement2(strs);
console.log(firstStr2); // string类型
-
定义泛型
使用
<T>
(T 是约定俗成的名称,也可用其他字母)来声明一个类型变量,代表"某种未知类型"。tsfunction identity<T>(arg: T): T { return arg; }
-
调用泛型函数
-
自动推导:TypeScript 会根据传入的值自动推断类型。
tsconst result = identity("hello"); // T 被推断为 string
-
手动指定:你也可以显式指定类型。
tsconst result = identity<number>(42); // T 是 number
-
-
应用于数组、对象等 常用于处理多种类型的数组或结构,保持类型安全。
tsfunction getFirst<T>(arr: T[]): T | undefined { return arr[0]; } getFirst([1, 2, 3]); // 返回 number | undefined getFirst(["a", "b"]); // 返回 string | undefined
1.4 实践:用泛型实现链表
让我们用一个更复杂的例子来加深理解 - 实现一个支持泛型的链表数据结构:
typescript
// 用泛型去声明一个链表
// 支持泛型的节点,可以接受value类型的传参
class NodeItem<T> {
value: T;
next: NodeItem<T> | null = null;
constructor(value: T) {
this.value = value;
}
}
class LinkedList<T> {
head: NodeItem<T> | null = null;
append(value: T): void {
const newNodeItem = new NodeItem(value);
if (!this.head) {
this.head = newNodeItem;
return;
}
let current = this.head;
while (current.next) {
current = current.next;
}
current.next = newNodeItem;
}
}
// 使用泛型链表存储数字
const numberList = new LinkedList<number>();
numberList.append(1);
numberList.append(2);
numberList.append(3);
console.log(numberList);
// 使用泛型链表存储自定义类型
interface User {
id: number;
name: string;
}
const userList = new LinkedList<User>();
userList.append({
id: 1,
name: '张三',
});
这个例子展示了泛型的强大之处:我们可以创建可重用的数据结构,同时保持完整的类型安全!
第二部分:type 与 interface - 类型的艺术
在 TypeScript 中,我们有两种主要方式来定义自定义类型:
type
和interface
。它们看似相似,实则各有特点。
2.1 基本概念与使用
✅ interface
(接口)
-
作用:定义对象的形状(有哪些属性、方法及其类型)。
-
语法:
tsinterface Person { name: string; age: number; }
-
使用:
tsconst user: Person = { name: '张三', age: 25 };
特点:支持扩展 (继承)和合并(同名接口自动合并)。
✅ type
(类型别名)
-
作用:给一个类型起个别名,可以是对象、联合类型、原始类型等。
-
语法:
tstype Person = { name: string; age: number; }
-
使用:
tsconst user: Person = { name: '李四', age: 30 };
特点:更灵活,可定义联合类型 、元组等复杂类型。
typescript
// interface 关键字
interface UserDemo {
name: string;
age: number;
}
// type 关键字
type UserType = {
name: string;
age: number;
}
// 使用方式相同
const u1: UserDemo = { name: '张三', age: 18 };
const u2: UserType = { name: '李四', age: 19 };
2.2 相同点:都能描述对象和函数
都可以描述一个对象或者函数:
typescript
// 描述函数类型
interface AddFn {
(a: number, b: number): number;
}
type AddType = (a: number, b: number) => number;
// 描述对象类型
interface Person {
name: string;
age: number;
}
type PersonType = {
name: string;
age: number;
};
都允许扩展:
typescript
// interface 扩展
interface Person {
name: string;
}
interface Employee extends Person {
job: string;
}
// type 扩展
type PersonType = {
name: string;
};
type EmployeeType = PersonType & {
job: string;
};
2.3 不同点:各有所长
1. 继承方式不同
typescript
// interface 使用 extends
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// type 使用 & 交集运算符
type AnimalType = {
name: string;
};
type DogType = AnimalType & {
breed: string;
};
2. 声明合并能力不同
typescript
// interface 可以重复声明,会自动合并
interface Animal {
name: string;
}
interface Animal {
age: number;
}
const dog: Animal = { name: '旺财', age: 1 }; // 正确
// type 不能重复声明
type AnimalType = { name: string };
// type AnimalType = { age: number }; // 错误:重复标识符
3. 应用范围不同
typescript
// type 可以用于定义基础类型、联合类型、元组等
type ID = number | string; // 联合类型
type Point = [number, number, string]; // 元组类型
type NumberAlias = number; // 类型别名
// interface 只能描述对象结构(函数、类)
// interface ID2 = number | string; // 错误!
4. 简单类型别名
typescript
// type 支持简单类型的别名
type NumberOther = number;
let c: NumberOther = 1;
// interface 不能为基本类型创建别名
// interface NumberInterface = number; // 错误!
2.4 如何选择:type 还是 interface?
一般建议:
- 优先使用
interface
,直到需要type
的特定功能 - 想定义对象结构?优先用
interface
(更直观、可扩展)。 - 需要联合类型或复杂类型?用
type
。 - 大多数场景下,两者可互换,按团队习惯选择即可。
总结
让我们通过一个表格来回顾一下 type 和 interface 的主要区别:
特性 | interface | type |
---|---|---|
扩展方式 | extends | & |
声明合并 | ✅ 支持 | ❌ 不支持 |
描述基本类型 | ❌ 不支持 | ✅ 支持 |
联合类型 | ❌ 不支持 | ✅ 支持 |
元组类型 | ❌ 不支持 | ✅ 支持 |
函数类型 | ✅ 支持 | ✅ 支持 |
类实现 | ✅ 支持 | ❌ 不支持 |
TypeScript 的类型系统就像是一座精妙的乐高城堡🏰,泛型、type 和 interface 是其中最重要的积木块。掌握它们,你就能构建出既安全又灵活的代码结构!
希望这篇文章能帮助你更好地理解 TypeScript 的类型系统。如果有任何问题,欢迎在评论区讨论!💬
延伸阅读: