🚀 TypeScript核心特性详解

TypeScript是JavaScript的超集,为JavaScript添加了静态类型检查。本文将通过生动的比喻和实战代码,带你深入理解TypeScript的核心特性。

📝 基本类型系统

🎯 类型系统的价值

TypeScript的类型系统就像代码的身份证,每个变量都有明确的身份标识,避免了运行时的类型错误。

🔤 基础数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
// 布尔值 - 真假判断
let isDone: boolean = false;

// 数字 - 支持十进制、十六进制、二进制、八进制
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;

// 字符串 - 支持模板字符串
let color: string = "blue";
let fullName: string = `Bob Bobbington`;
let sentence: string = `Hello, my name is ${fullName}`;

最佳实践:优先使用具体类型而不是 any,这样能获得更好的类型检查和代码提示。

📦 复合数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 数组 - 两种写法
let list: number[] = [1, 2, 3];
let listGeneric: Array<number> = [1, 2, 3];

// 元组 - 固定长度和类型的数组
let x: [string, number] = ["hello", 10];
console.log(x[0].substring(1)); // ✅ 知道是string类型
console.log(x[1].substring(1)); // ❌ 编译错误:number没有substring方法

// 枚举 - 为数值集合提供友好的名字
enum Color {
Red, // 0
Green, // 1
Blue // 2
}
let c: Color = Color.Green;
let colorName: string = Color[2]; // "Blue"

注意:元组类型在访问越界元素时会报错,这比JavaScript的运行时错误更早发现问题。

⚡ 特殊类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Any - 任意类型(尽量避免使用)
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // 可以,但失去了类型检查

// Void - 没有返回值
function warnUser(): void {
console.log("This is my warning message");
}

// Null 和 Undefined
let u: undefined = undefined;
let n: null = null;

// Never - 永远不会有返回值
function error(message: string): never {
throw new Error(message);
}

// 无限循环也是never类型
function infiniteLoop(): never {
while (true) {
// 永远不会结束
}
}

避坑指南any 类型会让你失去TypeScript的所有优势,除非在迁移老代码时,否则应该避免使用。


🔧 函数类型定义

💡 函数类型的本质

TypeScript允许为函数的输入输出分别定义类型,就像给函数贴上了使用说明书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 函数声明
function add(x: number, y: number): number {
return x + y;
}

// 函数表达式
let myAdd = function(x: number, y: number): number {
return x + y;
};

// 箭头函数
let myAdd2 = (x: number, y: number): number => x + y;

// 可选参数
function buildName(firstName: string, lastName?: string): string {
if (lastName) {
return firstName + " " + lastName;
} else {
return firstName;
}
}

// 默认参数
function buildName2(firstName: string, lastName = "Smith"): string {
return firstName + " " + lastName;
}

// 剩余参数
function buildName3(firstName: string, ...restOfName: string[]): string {
return firstName + " " + restOfName.join(" ");
}

类型推断:TypeScript很聪明,很多时候可以自动推断函数的返回类型,但显式声明能让代码更清晰。


🏗️ 接口与类型别名

🎭 接口的本质

接口就像合同条款,定义了对象必须遵守的结构规范。类可以通过 implements 签署这份合同。

📋 接口基础用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 基础接口定义
interface Person {
name: string;
age: number;
email?: string; // 可选属性
readonly id: number; // 只读属性
}

// 使用接口
const person: Person = {
id: 1,
name: "张三",
age: 25,
email: "zhangsan@example.com"
};

// person.id = 2; // ❌ 错误:id是只读属性

设计原则:接口应该描述对象的形状,而不是具体的实现细节。

🔗 接口继承与扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 基础接口
interface Person {
name: string;
age: number;
}

// 继承接口
interface Student extends Person {
studentId: string;
study(): void;
}

// 多重继承
interface Info {
phone: string;
address: string;
}

interface DetailedStudent extends Student, Info {
grade: number;
}

// 实现接口的类
class Freshman implements Student {
constructor(
public name: string,
public age: number,
public studentId: string
) {}

study(): void {
console.log(`${this.name} is studying`);
}
}

🏷️ 类型别名与联合类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 基础类型别名
type ID = string | number;
type Status = "pending" | "approved" | "rejected";

// 联合类型
interface Person {
name: string;
}

interface ContactInfo {
age: number;
phone: string | number; // 联合类型
}

// 交叉类型 - 合并多个类型
type Student = Person & ContactInfo & {
studentId: string;
};

const student: Student = {
name: "李四",
age: 20,
phone: "13800138000",
studentId: "2021001"
};

类型别名 vs 接口

  • 接口更适合定义对象的形状
  • 类型别名更适合联合类型、交叉类型等复杂类型

🛡️ 类型守卫 - 智能安检系统

🔍 类型守卫的本质

想象你有个快递站,要处理各种包裹(不同类型的数据)。类型守卫就像智能安检门,能自动识别包裹类型,决定放行到哪个处理通道。

🚪 四种类型守卫方式

🔤 typeof守卫 - 识别基本类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function printValue(val: string | number) {
if (typeof val === 'string') {
// 在这个分支中,TypeScript知道val是string类型
console.log('字符串长度:', val.length);
console.log('转大写:', val.toUpperCase());
} else {
// 在这个分支中,TypeScript知道val是number类型
console.log('数字平方:', val ** 2);
console.log('转字符串:', val.toString());
}
}

// 使用示例
printValue("Hello"); // 输出:字符串长度: 5, 转大写: HELLO
printValue(42); // 输出:数字平方: 1764, 转字符串: 42

适用场景:区分 stringnumberbooleanobject 等基本类型

🏷️ instanceof守卫 - 识别类实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Dog {
name: string;
constructor(name: string) {
this.name = name;
}
bark(): void {
console.log(`${this.name}: 汪汪汪!`);
}
}

class Cat {
name: string;
constructor(name: string) {
this.name = name;
}
meow(): void {
console.log(`${this.name}: 喵喵喵~`);
}
}

function animalSound(pet: Dog | Cat) {
if (pet instanceof Dog) {
pet.bark(); // ✅ TypeScript知道这是Dog实例
} else {
pet.meow(); // ✅ TypeScript知道这是Cat实例
}
}

// 使用示例
const myDog = new Dog("旺财");
const myCat = new Cat("咪咪");

animalSound(myDog); // 输出:旺财: 汪汪汪!
animalSound(myCat); // 输出:咪咪: 喵喵喵~

适用场景:区分不同类的实例,特别是在处理继承关系时

🔍 in守卫 - 检查对象属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
interface Bird {
name: string;
fly(): void;
}

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

function move(animal: Bird | Fish) {
if ('fly' in animal) {
// TypeScript知道animal有fly方法,所以是Bird类型
console.log(`${animal.name} 在天空中飞翔`);
animal.fly();
} else {
// TypeScript知道animal没有fly方法,所以是Fish类型
console.log(`${animal.name} 在水中游泳`);
animal.swim();
}
}

// 使用示例
const eagle: Bird = {
name: "老鹰",
fly() { console.log("展翅高飞!"); }
};

const goldfish: Fish = {
name: "金鱼",
swim() { console.log("自由游泳!"); }
};

move(eagle); // 输出:老鹰 在天空中飞翔, 展翅高飞!
move(goldfish); // 输出:金鱼 在水中游泳, 自由游泳!

适用场景:判断对象是否有特定属性,特别适合接口类型的区分

⚙️ 自定义守卫 - 高级安检仪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
interface Bird {
name: string;
fly(): void;
}

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

// 自定义类型守卫函数
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}

function isBird(pet: Fish | Bird): pet is Bird {
return (pet as Bird).fly !== undefined;
}

function handlePet(pet: Fish | Bird) {
if (isFish(pet)) {
console.log(`${pet.name} 是一条鱼`);
pet.swim(); // ✅ TypeScript知道这是Fish类型
} else if (isBird(pet)) {
console.log(`${pet.name} 是一只鸟`);
pet.fly(); // ✅ TypeScript知道这是Bird类型
}
}

// 更复杂的自定义守卫
interface ApiSuccess {
status: 'success';
data: any;
}

interface ApiError {
status: 'error';
message: string;
code: number;
}

function isApiSuccess(response: ApiSuccess | ApiError): response is ApiSuccess {
return response.status === 'success';
}

function handleApiResponse(response: ApiSuccess | ApiError) {
if (isApiSuccess(response)) {
console.log('请求成功:', response.data);
} else {
console.error(`请求失败 [${response.code}]: ${response.message}`);
}
}

自定义守卫的优势:可以封装复杂的类型判断逻辑,让代码更加清晰和可复用

🎯 实际应用场景

类型守卫应用

处理API返回数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface ApiSuccess {
status: 'success';
data: any;
}

interface ApiError {
status: 'error';
message: string;
}

function handleResponse(res: ApiSuccess | ApiError) {
if (res.status === 'success') {
console.log('成功:', res.data);
} else {
console.error('失败:', res.message);
}
}

操作DOM元素

1
2
3
4
5
6
7
8
const element = document.getElementById('btn');

if (element instanceof HTMLButtonElement) {
element.disabled = true; // ✅ 安全操作按钮元素
element.onclick = () => console.log('按钮被点击');
} else if (element instanceof HTMLInputElement) {
element.value = ''; // ✅ 安全操作输入框元素
}

处理联合类型参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type Shape = Circle | Square;

interface Circle {
kind: 'circle';
radius: number;
}

interface Square {
kind: 'square';
size: number;
}

function getArea(shape: Shape): number {
if (shape.kind === 'circle') {
return Math.PI * shape.radius ** 2;
} else {
return shape.size ** 2;
}
}

🎁 泛型 - 类型界的万能收纳盒

🧰 泛型的本质

泛型就像万能收纳盒,可以装任何类型的东西。你换个标签,盒子就能装不同类型的内容,既保持了灵活性,又保证了类型安全。

🔧 泛型的基本概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 普通写法:只能装字符串
type StringBox = {
content: string;
}

// 泛型写法:什么都能装
type MagicBox<T> = {
content: T;
}

// 使用示例
const toyBox: MagicBox<string> = { content: '乐高积木' };
const priceBox: MagicBox<number> = { content: 299 };
const statusBox: MagicBox<boolean> = { content: true };

🎯 三大应用场景

⚙️ 函数泛型 - 咖啡机原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 普通函数:只能处理字符串
function processString(input: string): string {
return input.toUpperCase();
}

// 泛型函数:可以处理任何类型
function process<T>(input: T): T {
console.log('正在处理:', input);
return input;
}

// 使用示例
const str = process<string>('hello'); // 返回 string 类型
const num = process<number>(42); // 返回 number 类型
const bool = process<boolean>(true); // 返回 boolean 类型

// 更实用的例子:数组操作
function getFirst<T>(arr: T[]): T | undefined {
return arr.length > 0 ? arr[0] : undefined;
}

const firstNumber = getFirst([1, 2, 3]); // number | undefined
const firstName = getFirst(['a', 'b', 'c']); // string | undefined
const firstBool = getFirst([true, false]); // boolean | undefined

类型推断:很多时候TypeScript可以自动推断泛型类型,不需要显式指定 <T>

🔌 接口泛型 - 万能插座

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 泛型接口定义
interface Container<T> {
value: T;
getValue(): T;
setValue(value: T): void;
}

// 实现泛型接口
class Box<T> implements Container<T> {
constructor(private _value: T) {}

getValue(): T {
return this._value;
}

setValue(value: T): void {
this._value = value;
}

get value(): T {
return this._value;
}
}

// 使用示例
const stringBox = new Box<string>('Hello TypeScript');
const numberBox = new Box<number>(42);

console.log(stringBox.getValue().toUpperCase()); // ✅ 知道是string类型
console.log(numberBox.getValue().toFixed(2)); // ✅ 知道是number类型

// API响应的泛型接口
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}

interface User {
id: number;
name: string;
email: string;
}

const userResponse: ApiResponse<User> = {
code: 200,
message: 'success',
data: {
id: 1,
name: '张三',
email: 'zhangsan@example.com'
}
};

📦 类泛型 - 变形快递箱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 泛型类定义
class DataStore<T> {
private items: T[] = [];

add(item: T): void {
this.items.push(item);
}

get(index: number): T | undefined {
return this.items[index];
}

getAll(): T[] {
return [...this.items];
}

find(predicate: (item: T) => boolean): T | undefined {
return this.items.find(predicate);
}

filter(predicate: (item: T) => boolean): T[] {
return this.items.filter(predicate);
}
}

// 使用示例
interface Product {
id: number;
name: string;
price: number;
}

const productStore = new DataStore<Product>();

productStore.add({ id: 1, name: 'iPhone', price: 6999 });
productStore.add({ id: 2, name: 'iPad', price: 3999 });

// 类型安全的操作
const iPhone = productStore.find(p => p.name === 'iPhone');
const expensiveProducts = productStore.filter(p => p.price > 5000);

console.log(iPhone?.price); // ✅ TypeScript知道iPhone可能是Product或undefined

🛠️ 泛型约束与高级用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 泛型约束:限制泛型必须有某些属性
interface Lengthwise {
length: number;
}

function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // ✅ 现在知道arg有length属性
return arg;
}

logLength('hello'); // ✅ string有length属性
logLength([1, 2, 3]); // ✅ array有length属性
logLength({ length: 10 }); // ✅ 对象有length属性
// logLength(123); // ❌ number没有length属性

// 键值约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}

const person = { name: '张三', age: 25, city: '北京' };

const name = getProperty(person, 'name'); // string类型
const age = getProperty(person, 'age'); // number类型
// const invalid = getProperty(person, 'salary'); // ❌ 编译错误

🎨 实用工具类型

🔧 TypeScript内置工具类型

TypeScript提供了许多实用的工具类型,就像装修工具箱,每个工具都有特定的用途。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}

// 1. Partial<T> - 所有属性变为可选
type PartialUser = Partial<User>;
// 等价于:{ id?: number; name?: string; email?: string; ... }

// 2. Required<T> - 所有属性变为必需
type RequiredUser = Required<PartialUser>;

// 3. Pick<T, K> - 选择部分属性
type UserProfile = Pick<User, 'id' | 'name' | 'email'>;
// 等价于:{ id: number; name: string; email: string; }

// 4. Omit<T, K> - 排除部分属性
type SafeUser = Omit<User, 'password'>;
// 等价于:{ id: number; name: string; email: string; createdAt: Date; }

// 5. Record<K, T> - 创建键值对类型
type UserRoles = Record<string, string[]>;
// 等价于:{ [key: string]: string[] }

// 实际应用示例
function updateUser(id: number, updates: Partial<User>): User {
// 只需要传入要更新的字段
const existingUser = getUserById(id);
return { ...existingUser, ...updates };
}

// 使用示例
updateUser(1, { name: '李四' }); // ✅ 只更新名字
updateUser(1, { email: 'new@email.com' }); // ✅ 只更新邮箱

🎯 总结与最佳实践

💡 TypeScript核心价值

  1. 类型安全:编译时发现错误,避免运行时崩溃
  2. 代码提示:IDE能提供精准的代码补全和重构支持
  3. 文档化:类型本身就是最好的文档
  4. 重构友好:修改类型定义时,相关代码会自动报错提醒

⚠️ 常见陷阱与避坑指南

  1. **避免滥用 any**:相当于关闭了类型检查,失去TypeScript的优势
  2. 合理使用类型断言:只在你确定类型时使用,过度使用会带来风险
  3. 善用类型守卫:让TypeScript更好地理解你的代码逻辑
  4. 泛型约束很重要:不要让泛型过于宽泛,适当的约束能提供更好的类型安全

🚀 进阶学习建议

  1. 实践为主:在实际项目中使用TypeScript,体验类型系统的价值
  2. 阅读优秀代码:学习开源项目中的TypeScript用法
  3. 关注新特性:TypeScript在不断发展,新版本会带来更强大的类型系统
  4. 工具链配置:学会配置tsconfig.json,使用合适的编译选项

🎓 学习路径总结

1
基础类型 → 函数类型 → 接口定义 → 类型守卫 → 泛型应用 → 高级类型

就像学习一门新语言,从基础语法开始,逐步掌握高级特性,最终能够流畅地表达复杂的类型关系。🚀


基本类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Boolean
let isDone: boolean = false;

// Number
let decimal: number = 6;

// String
let color: string = "blue";

// Array
let list: number[] = [1, 2, 3];
let listGeneric: Array<number> = [1, 2, 3];

// Tuple
let x: [string, number] = ["hello", 10];

// Enum
enum Color {Red, Green, Blue}
let c: Color = Color.Green;

// Any
let notSure: any = 4;

// Void
function warnUser(): void {
console.log("This is my warning message");
}

// Null and Undefined
let u: undefined = undefined;
let n: null = null;

// Never 永远不会正常返回
function error(message: string): never {
throw new Error(message);
}

TypeScript 允许为函数的输入输出分别定义类型,写法跟python 很像

function add(x: number, y: number): number {
return x + y;
}

let myAdd = function(x: number, y: number): number { return x + y; };

接口,有点像抽象类,可以通过extends继续拓展,而类可以通过implements继承接口(需要实现接口中的所有方法,除可选方法)
interface Person {
name: string;
}
interface student extends Person {
study(): void;
}
class Freshman implements student {
constructor(public name: string) {}
study() {
console.log(${this.name} is studying);
}
}
实际上,接口还可以直接被实现,只需要指定复杂变量的类型即可
如:
interface Person {
name: string;
}
interface Info{
age: number;
pNumber: string | number;//这里使用了二选一的联合类型
}
type Student = Person & Info;
const student: Student = {

name: "John",
age: 18,
pNumber: 1234567890

}

枚举
emun Color {Red, Green, Blue}
const c: Color = Color.Green;

类型守卫
来,用最接地气的方式给你讲透TS类型守卫,看完你绝对能怼面试官脸上:


一、类型守卫是啥?【安检员思维】

想象你有个快递站,要处理各种包裹(不同类型的数据)。类型守卫就像智能安检门,能自动识别包裹类型,决定放行到哪个处理通道:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 普通安检(无类型守卫)
function handlePackage(pkg: any) {
pkg.open() // 可能爆炸💥,因为不知道里面是啥
}

// 智能安检(带类型守卫)
function handlePackage(pkg: string | Bomb) {
if (typeof pkg === 'string') { // 👉🏻这就是类型守卫
console.log('普通包裹:', pkg.toUpperCase())
} else {
console.log('危险品:', pkg.explode())
}
}

这安检门(typeof)一眼看穿包裹类型,把危险品和普通包裹分开处理,避免运行时爆炸15


二、四大常用姿势(附实战代码)

1. typeof守卫:识别基本类型

1
2
3
4
5
6
7
function printValue(val: string | number) {
if (typeof val === 'string') {
console.log('字符串长度:', val.length) // ✅自动识别string类型
} else {
console.log('数字平方:', val ** 2) // ✅自动识别number类型
}
}

适合场景:区分string/number/boolean等基本类型14

2. instanceof守卫:识别类实例

1
2
3
4
5
6
7
8
9
10
class Dog { bark() {} }
class Cat { meow() {} }

function animalSound(pet: Dog | Cat) {
if (pet instanceof Dog) {
pet.bark() // ✅知道这是狗
} else {
pet.meow() // ✅知道这是猫
}
}

适合场景:区分不同类的实例14

3. in守卫:检查对象属性

1
2
3
4
5
6
7
8
9
10
interface Bird { fly: () => void }
interface Fish { swim: () => void }

function move(animal: Bird | Fish) {
if ('fly' in animal) {
animal.fly() // ✅识别为鸟
} else {
animal.swim() // ✅识别为鱼
}
}

适合场景:判断对象是否有特定属性35

4. 自定义守卫:高级安检仪

1
2
3
4
5
6
7
8
9
10
11
12
// 自定义安检规则(类型谓词函数)
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined // 👈🏻告诉TS这是Fish类型
}

function handlePet(pet: Fish | Bird) {
if (isFish(pet)) {
pet.swim() // ✅TS知道这是鱼
} else {
pet.fly() // ✅自动识别为鸟
}
}

适合场景:复杂类型判断(比如接口/联合类型)35


三、什么时候用?【真实场景】

  1. 处理API返回数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    interface Success { data: string }
    interface Error { code: number }

    function handleResponse(res: Success | Error) {
    if ('data' in res) {
    console.log('成功:', res.data)
    } else {
    console.error('失败:', res.code)
    }
    }
  2. 操作DOM元素

    1
    2
    3
    4
    const element = document.getElementById('btn')
    if (element instanceof HTMLButtonElement) {
    element.disabled = true // ✅安全操作按钮元素
    }
  3. 处理联合类型参数

    1
    2
    3
    4
    5
    6
    7
    type Shape = Circle | Square
    function getArea(shape: Shape) {
    if (shape.kind === 'circle') { // 字面量守卫
    return Math.PI * shape.radius ** 2
    }
    return shape.size ** 2
    }

四、避坑指南

  • **别滥用any**:相当于拆了安检门,什么包裹都放行
  • 优先用===判断字面量:比typeof更精准
  • 复杂对象用自定义守卫:比in操作符更可靠
  • 联合类型必用守卫:否则TS会报类型错误

五、总结:类型守卫的好处

  1. 代码更安全:编译阶段发现类型错误,不用等运行时崩溃
  2. 提示更智能:类型判断后,代码补全精准到具体类型
  3. 逻辑更清晰:条件分支显式处理不同类型
  4. 重构更放心:修改类型定义时,守卫会自动报错提醒

就像给代码装了个智能安检系统,既保证安全又不影响效率25。下次写TS时,记得给你的变量加个守卫!

泛型
用最接地气的方式给你讲透TS泛型,保证你听完就能用:

1. 泛型就是类型界的「万能收纳盒」 15

想象你有个收纳盒,可以装任何东西:

1
2
3
4
5
6
7
8
9
// 普通写法:只能装衣服
type Closet = string[]

// 泛型写法:什么都能装
type MagicBox<T> = T[]

// 用法:
const toyBox: MagicBox<string> = ['乐高', '手办'] // 装玩具
const bookBox: MagicBox<number> = [100, 200] // 装书的价格

这里的T就像盒子的标签,告诉别人里面装的是什么。你换个标签,盒子就能装不同类型的东西。

2. 为什么要用?防止「货不对板」 35

没有泛型就像快递员乱拆包裹:

1
2
3
4
5
6
7
8
9
function sendPackage(content: any) {
// 可能收到狗屎,但编译时不知道
}
// 用了泛型就像贴快递单:
function sendPackage<T>(content: T) {
// 收到时检查标签,不对就拒收
}
sendPackage<string>('正常快递') // ✅
sendPackage<string>(666) // ❌ 直接报错

3. 三大常用姿势

(1) 函数泛型:咖啡机原理 16

1
2
3
4
5
6
7
8
9
10
// 普通咖啡机只能做美式
function makeCoffee(beans: string): string {
return '美式咖啡'
}

// 泛型咖啡机:换咖啡豆就能出不同口味
function makeCoffee<T extends CoffeeBean>(beans: T): T {
return beans.process()
}
makeCoffee<ArabicaBean>(new ArabicaBean()) // 得到阿拉比卡风味

(2) 接口泛型:万能插座 46

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Charger<T> {
plugIn: (device: T) => void
}

// 苹果充电口
const iPhoneCharger: Charger<Lightning> = {
plugIn: (phone) => phone.connect()
}

// Type-C充电口
const androidCharger: Charger<TypeC> = {
plugIn: (phone) => phone.charge()
}

(3) 类泛型:变形快递箱 56

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class DeliveryBox<T> {
private content: T

constructor(stuff: T) {
this.content = stuff
}

// 拆箱时自动识别内容
open(): T {
return this.content
}
}

const catBox = new DeliveryBox<Cat>(new Cat())
const bookBox = new DeliveryBox<string>('百年孤独')

catBox.open().meow() // ✅ 直接调用猫的方法
bookBox.open().meow() // ❌ 报错:字符串没有meow方法

4. 实用技巧:四大神器 2

1
2
3
4
5
6
7
8
9
10
11
// 1. 装修工具Partial:把房子变成毛坯房(所有属性可选)
type RoughHouse = Partial<House>

// 2. 安检工具Required:必须带齐证件(所有属性必填)
type StrictID = Required<IDCard>

// 3. 摘菜工具Pick:只拿需要的菜(选择部分属性)
type VegRecipe = Pick<Recipe, 'tomato' | 'egg'>

// 4. 垃圾分类Omit:扔掉有害垃圾(排除某些属性)
type SafeFood = Omit<Food, 'preservatives'>

5. 什么时候该用?

  • 当你的函数/类要处理多种类型数据时(比如通用工具函数)5
  • 当你要创建可复用组件时(比如表格组件适配不同数据类型)6
  • 当你要约束数据结构时(比如API返回格式统一但数据不同)1

6. 新人常见坑位

  • 乱用any:就像拆包裹不带手套,可能摸到💩
  • 不写泛型约束:像不锁车厢的货车,货物可能中途掉落
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 错误示范:什么都能传进来
    function drive<T>(car: T) {
    car.run() // 可能传进来的是自行车,没有run方法
    }

    // 正确姿势:加约束
    function drive<T extends Vehicle>(car: T) {
    car.run() // ✅ 确保传进来的都有run方法
    }

总结一张图:

1
2
普通代码 → 写死类型 → 只能处理单一场景
泛型代码 → 类型参数化 → 一套代码处理N种类型

就像瑞士军刀,普通刀只能切东西,泛型刀换个刀头就能变成剪刀/螺丝刀/开瓶器。🔪✨