🚀 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
基础类型 → 函数类型 → 接口定义 → 类型守卫 → 泛型应用 → 高级类型

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