🚀 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)); console.log(x[1].substring(1));
enum Color { Red, Green, Blue } let c: Color = Color.Green; let colorName: string = Color[2];
|
注意:元组类型在访问越界元素时会报错,这比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
| let notSure: any = 4; notSure = "maybe a string instead"; notSure = false;
function warnUser(): void { console.log("This is my warning message"); }
let u: undefined = undefined; let n: null = null;
function error(message: string): never { throw new Error(message); }
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" };
|
设计原则:接口应该描述对象的形状,而不是具体的实现细节。
🔗 接口继承与扩展
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') { console.log('字符串长度:', val.length); console.log('转大写:', val.toUpperCase()); } else { console.log('数字平方:', val ** 2); console.log('转字符串:', val.toString()); } }
printValue("Hello"); printValue(42);
|
适用场景:区分 string
、number
、boolean
、object
等基本类型
🏷️ 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(); } else { pet.meow(); } }
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) { console.log(`${animal.name} 在天空中飞翔`); animal.fly(); } else { 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(); } else if (isBird(pet)) { console.log(`${pet.name} 是一只鸟`); pet.fly(); } }
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}`); } }
|
自定义守卫的优势:可以封装复杂的类型判断逻辑,让代码更加清晰和可复用
🎯 实际应用场景
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); } }
|
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'); const num = process<number>(42); const bool = process<boolean>(true);
function getFirst<T>(arr: T[]): T | undefined { return arr.length > 0 ? arr[0] : undefined; }
const firstNumber = getFirst([1, 2, 3]); const firstName = getFirst(['a', 'b', 'c']); const firstBool = getFirst([true, false]);
|
类型推断:很多时候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()); console.log(numberBox.getValue().toFixed(2));
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);
|
🛠️ 泛型约束与高级用法
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); return arg; }
logLength('hello'); logLength([1, 2, 3]); logLength({ length: 10 });
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'); const age = getProperty(person, 'age');
|
🎨 实用工具类型
🔧 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; }
type PartialUser = Partial<User>;
type RequiredUser = Required<PartialUser>;
type UserProfile = Pick<User, 'id' | 'name' | 'email'>;
type SafeUser = Omit<User, 'password'>;
type UserRoles = Record<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核心价值
- 类型安全:编译时发现错误,避免运行时崩溃
- 代码提示:IDE能提供精准的代码补全和重构支持
- 文档化:类型本身就是最好的文档
- 重构友好:修改类型定义时,相关代码会自动报错提醒
⚠️ 常见陷阱与避坑指南
- **避免滥用
any
**:相当于关闭了类型检查,失去TypeScript的优势
- 合理使用类型断言:只在你确定类型时使用,过度使用会带来风险
- 善用类型守卫:让TypeScript更好地理解你的代码逻辑
- 泛型约束很重要:不要让泛型过于宽泛,适当的约束能提供更好的类型安全
🚀 进阶学习建议
- 实践为主:在实际项目中使用TypeScript,体验类型系统的价值
- 阅读优秀代码:学习开源项目中的TypeScript用法
- 关注新特性:TypeScript在不断发展,新版本会带来更强大的类型系统
- 工具链配置:学会配置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
| let isDone: boolean = false;
let decimal: number = 6;
let color: string = "blue";
let list: number[] = [1, 2, 3]; let listGeneric: Array<number> = [1, 2, 3];
let x: [string, number] = ["hello", 10];
enum Color {Red, Green, Blue} let c: Color = Color.Green;
let notSure: any = 4;
function warnUser(): void { console.log("This is my warning message"); }
let u: undefined = undefined; let n: null = null;
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) } else { console.log('数字平方:', val ** 2) } }
|
适合场景:区分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 }
function handlePet(pet: Fish | Bird) { if (isFish(pet)) { pet.swim() } else { pet.fly() } }
|
适合场景:复杂类型判断(比如接口/联合类型)35。
三、什么时候用?【真实场景】
处理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) } }
|
操作DOM元素:
1 2 3 4
| const element = document.getElementById('btn') if (element instanceof HTMLButtonElement) { element.disabled = true }
|
处理联合类型参数:
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会报类型错误
五、总结:类型守卫的好处
- 代码更安全:编译阶段发现类型错误,不用等运行时崩溃
- 提示更智能:类型判断后,代码补全精准到具体类型
- 逻辑更清晰:条件分支显式处理不同类型
- 重构更放心:修改类型定义时,守卫会自动报错提醒
就像给代码装了个智能安检系统,既保证安全又不影响效率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() }
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()
|
4. 实用技巧:四大神器 2
1 2 3 4 5 6 7 8 9 10 11
| type RoughHouse = Partial<House>
type StrictID = Required<IDCard>
type VegRecipe = Pick<Recipe, 'tomato' | 'egg'>
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() }
function drive<T extends Vehicle>(car: T) { car.run() }
|
总结一张图:
1 2
| 普通代码 → 写死类型 → 只能处理单一场景 泛型代码 → 类型参数化 → 一套代码处理N种类型
|
就像瑞士军刀,普通刀只能切东西,泛型刀换个刀头就能变成剪刀/螺丝刀/开瓶器。🔪✨