====== 第五章:接口 ====== ===== 本章概述 ===== 接口(Interface)是 TypeScript 最核心的特性之一,它定义了对象的"形状",提供了强大的类型约束能力。本章将深入讲解接口的定义、使用和各种高级特性。 ===== 5.1 接口基础 ===== ==== 5.1.1 什么是接口 ==== 接口是对对象结构的描述,它定义了对象应该包含哪些属性和方法。 ```typescript // 基本接口定义 interface Person { name: string; age: number; } // 使用接口 const alice: Person = { name: "Alice", age: 25 }; // 类型检查 const bob: Person = { name: "Bob", age: 30 }; ``` ==== 5.1.2 接口与类型别名的区别 ==== ```typescript // 接口 interface Point { x: number; y: number; } // 类型别名 type Point2 = { x: number; y: number; }; // 关键区别1:接口可以声明合并 interface Point { z?: number; // 扩展 Point 接口 } // 关键区别2:接口更适合面向对象编程 interface Animal { name: string; } interface Dog extends Animal { breed: string; } // 关键区别3:类型别名可以表示联合类型 type Status = "loading" | "success" | "error"; ``` ===== 5.2 接口属性 ===== ==== 5.2.1 必需属性 ==== ```typescript interface User { id: number; name: string; email: string; } const user: User = { id: 1, name: "Alice", email: "alice@example.com" }; // 缺少属性会报错 // const incomplete: User = { // id: 1, // name: "Alice" // // Error: Property 'email' is missing // }; ``` ==== 5.2.2 可选属性(Optional Properties) ==== 使用 ? 标记可选属性: ```typescript interface User { id: number; name: string; email?: string; // 可选 phone?: string; // 可选 address?: { city: string; country: string; }; } // 可以省略可选属性 const user1: User = { id: 1, name: "Alice" }; const user2: User = { id: 2, name: "Bob", email: "bob@example.com" }; ``` ==== 5.2.3 只读属性(Readonly Properties) ==== 使用 readonly 标记只读属性: ```typescript interface Config { readonly apiUrl: string; readonly version: string; timeout: number; // 可修改 } const config: Config = { apiUrl: "https://api.example.com", version: "1.0.0", timeout: 5000 }; config.timeout = 10000; // OK // config.apiUrl = "..."; // Error: Cannot assign to 'apiUrl' // 只读数组 interface TodoList { readonly items: readonly string[]; } const list: TodoList = { items: ["Buy milk", "Walk dog"] }; // list.items.push("New item"); // Error // list.items[0] = "Changed"; // Error ``` ===== 5.3 函数类型 ===== ==== 5.3.1 接口定义函数类型 ==== ```typescript // 定义函数接口 interface SearchFunc { (source: string, subString: string): boolean; } // 实现 const mySearch: SearchFunc = function(source, subString) { return source.search(subString) > -1; }; // 使用 console.log(mySearch("hello world", "world")); // true ``` ==== 5.3.2 带属性的函数 ==== ```typescript interface Counter { (start: number): string; // 调用签名 interval: number; // 属性 reset(): void; // 方法 } function createCounter(): Counter { const counter = function(start: number): string { return `Started at ${start}`; } as Counter; counter.interval = 1000; counter.reset = function() { console.log("Counter reset"); }; return counter; } const c = createCounter(); console.log(c(10)); // "Started at 10" console.log(c.interval); // 1000 c.reset(); ``` ==== 5.3.3 构造函数签名 ==== ```typescript interface ClockConstructor { new (hour: number, minute: number): ClockInterface; } interface ClockInterface { tick(): void; } class DigitalClock implements ClockInterface { constructor(h: number, m: number) {} tick() { console.log("beep beep"); } } class AnalogClock implements ClockInterface { constructor(h: number, m: number) {} tick() { console.log("tick tock"); } } function createClock( ctor: ClockConstructor, hour: number, minute: number ): ClockInterface { return new ctor(hour, minute); } const digital = createClock(DigitalClock, 12, 17); const analog = createClock(AnalogClock, 7, 32); ``` ===== 5.4 索引签名(Index Signatures) ===== ==== 5.4.1 字符串索引签名 ==== ```typescript // 定义字符串索引签名 interface StringDictionary { [key: string]: string; } const dict: StringDictionary = { name: "Alice", country: "China" }; // 使用 console.log(dict["name"]); dict["city"] = "Beijing"; ``` ==== 5.4.2 数字索引签名 ==== ```typescript interface NumberArray { [index: number]: string; } const arr: NumberArray = ["a", "b", "c"]; console.log(arr[0]); // "a" ``` ==== 5.4.3 混合索引签名 ==== ```typescript interface HybridDictionary { [key: string]: string | number; [index: number]: string; // 数字索引的返回值必须是字符串索引的子类型 // 预定义属性 name: string; // 必须兼容索引签名 } const hybrid: HybridDictionary = { name: "test", 0: "zero", 1: "one", other: "value" }; ``` ==== 5.4.4 只读索引签名 ==== ```typescript interface ReadonlyDictionary { readonly [key: string]: number; } const readonlyDict: ReadonlyDictionary = { x: 10, y: 20 }; // readonlyDict["x"] = 100; // Error ``` ===== 5.5 接口继承 ===== ==== 5.5.1 单继承 ==== ```typescript interface Animal { name: string; } interface Dog extends Animal { breed: string; } const myDog: Dog = { name: "Buddy", breed: "Golden Retriever" }; ``` ==== 5.5.2 多继承 ==== ```typescript interface Shape { color: string; } interface PenStroke { penWidth: number; } interface Square extends Shape, PenStroke { sideLength: number; } const square: Square = { color: "blue", penWidth: 5, sideLength: 10 }; ``` ==== 5.5.3 继承时的类型兼容性 ==== ```typescript interface Animal { name: string; } interface Dog extends Animal { breed: string; bark(): void; } // 子类型可以赋值给父类型 const dog: Dog = { name: "Buddy", breed: "Labrador", bark() { console.log("Woof!"); } }; const animal: Animal = dog; // OK // 父类型不能赋值给子类型 // const wrongDog: Dog = { name: "Wrong" }; // Error ``` ===== 5.6 接口与类 ===== ==== 5.6.1 类实现接口 ==== ```typescript interface ClockInterface { currentTime: Date; setTime(d: Date): void; } class Clock implements ClockInterface { currentTime: Date = new Date(); setTime(d: Date): void { this.currentTime = d; } constructor(h: number, m: number) {} } ``` ==== 5.6.2 多个接口 ==== ```typescript interface Printable { print(): void; } interface Loggable { log(): void; } class DocumentClass implements Printable, Loggable { print(): void { console.log("Printing document..."); } log(): void { console.log("Logging document activity..."); } } ``` ===== 5.7 接口的高级特性 ===== ==== 5.7.1 可选方法的接口 ==== ```typescript interface Plugin { name: string; init(): void; destroy?(): void; // 可选方法 update?(config: object): void; // 可选方法 } const simplePlugin: Plugin = { name: "Simple", init() { console.log("Initialized"); } // destroy 和 update 是可选的 }; const advancedPlugin: Plugin = { name: "Advanced", init() { console.log("Initialized"); }, destroy() { console.log("Destroyed"); }, update(config) { console.log("Updated", config); } }; ``` ==== 5.7.2 接口的声明合并 ==== ```typescript interface Window { title: string; } interface Window { ts: TypeScriptAPI; } // 结果等同于: // interface Window { // title: string; // ts: TypeScriptAPI; // } interface TypeScriptAPI { version: string; } const src: Window = { title: "TypeScript", ts: { version: "5.0" } }; ``` ==== 5.7.3 严格的属性检查 ==== ```typescript interface SquareConfig { color?: string; width?: number; } function createSquare(config: SquareConfig): { color: string; area: number } { return { color: config.color || "red", area: config.width ? config.width * config.width : 20 }; } // 正常的对象字面量会经过额外属性检查 // const mySquare = createSquare({ colour: "red", width: 100 }); // Error: 'colour' not in 'SquareConfig' // 解决方法1:使用类型断言 const mySquare = createSquare({ colour: "red", width: 100 } as SquareConfig); // 解决方法2:添加字符串索引签名 interface SquareConfig2 { color?: string; width?: number; [propName: string]: any; } // 解决方法3:将对象赋值给变量 const squareOptions = { colour: "red", width: 100 }; const mySquare2 = createSquare(squareOptions); ``` ===== 5.8 接口的实际应用 ===== ==== 5.8.1 API 响应类型 ==== ```typescript // 基础响应接口 interface ApiResponse { code: number; message: string; data: T; timestamp: number; } // 用户相关 interface User { id: number; name: string; email: string; avatar?: string; } interface UserResponse extends ApiResponse {} interface UsersResponse extends ApiResponse {} // 使用 async function fetchUser(id: number): Promise { const response = await fetch(`/api/users/${id}`); return response.json(); } // 类型安全的响应处理 const userResponse: UserResponse = await fetchUser(1); console.log(userResponse.data.name); // TypeScript 知道 data 是 User ``` ==== 5.8.2 组件 Props 接口 ==== ```typescript // React/Vue 组件 Props 定义 interface ButtonProps { // 必需属性 label: string; onClick: () => void; // 可选属性 variant?: "primary" | "secondary" | "danger"; size?: "small" | "medium" | "large"; disabled?: boolean; loading?: boolean; // 样式 className?: string; style?: React.CSSProperties; // HTML 属性扩展 type?: "button" | "submit" | "reset"; } // 使用 function Button(props: ButtonProps) { const { label, onClick, variant = "primary", size = "medium", disabled = false, loading = false } = props; return ( ); } ``` ===== 5.9 本章小结 ===== 本章我们学习了: 1. **接口基础** - 定义对象结构,与类型别名的区别 2. **属性类型** - 必需属性、可选属性、只读属性 3. **函数类型** - 函数签名、带属性的函数、构造函数 4. **索引签名** - 字符串和数字索引签名 5. **接口继承** - 单继承和多继承 6. **类实现接口** - implements 关键字 7. **高级特性** - 可选方法、声明合并、严格属性检查 ===== 5.10 练习题 ===== ==== 练习 1:接口设计 ==== 设计一个完整的电商系统类型接口: - Product(商品):id, name, price, category, description?, imageUrl?, inStock - CartItem(购物车项):product, quantity - ShoppingCart(购物车):items, totalPrice, addItem(), removeItem(), clear() - Order(订单):id, items, totalAmount, status, createdAt, shippingAddress
参考答案 ```typescript // 基础类型 interface Product { readonly id: string; name: string; price: number; category: string; description?: string; imageUrl?: string; inStock: boolean; } // 购物车项 interface CartItem { readonly product: Product; quantity: number; } // 购物车 interface ShoppingCart { readonly items: readonly CartItem[]; readonly totalPrice: number; addItem(product: Product, quantity: number): void; removeItem(productId: string): void; updateQuantity(productId: string, quantity: number): void; clear(): void; } // 订单状态 type OrderStatus = "pending" | "confirmed" | "shipped" | "delivered" | "cancelled"; // 地址 interface Address { street: string; city: string; state: string; zipCode: string; country: string; } // 订单 interface Order { readonly id: string; readonly items: readonly CartItem[]; readonly totalAmount: number; status: OrderStatus; readonly createdAt: Date; shippingAddress: Address; trackingNumber?: string; } // API 响应 interface ApiResponse { success: boolean; data?: T; error?: string; } type ProductResponse = ApiResponse; type ProductsResponse = ApiResponse; type OrderResponse = ApiResponse; ```
==== 练习 2:函数接口 ==== 实现一个事件系统接口: ```typescript // 要求: // 1. 定义 EventEmitter 接口 // 2. 支持 on(event, listener) 订阅事件 // 3. 支持 off(event, listener) 取消订阅 // 4. 支持 emit(event, ...args) 触发事件 // 5. 支持 once(event, listener) 一次性订阅 ```
参考答案 ```typescript // 事件监听器类型 type EventListener = (...args: T) => void; // 事件发射器接口 interface EventEmitter { on(event: string, listener: EventListener): void; off(event: string, listener: EventListener): void; emit(event: string, ...args: T): void; once(event: string, listener: EventListener): void; } // 实现 class SimpleEventEmitter implements EventEmitter { private listeners: Map> = new Map(); private onceListeners: Map> = new Map(); on(event: string, listener: EventListener): void { if (!this.listeners.has(event)) { this.listeners.set(event, new Set()); } this.listeners.get(event)!.add(listener as EventListener); } off(event: string, listener: EventListener): void { this.listeners.get(event)?.delete(listener as EventListener); this.onceListeners.get(event)?.delete(listener as EventListener); } emit(event: string, ...args: T): void { const eventListeners = this.listeners.get(event); const onceEventListeners = this.onceListeners.get(event); eventListeners?.forEach(listener => listener(...args)); onceEventListeners?.forEach(listener => { listener(...args); this.onceListeners.get(event)?.delete(listener); }); } once(event: string, listener: EventListener): void { if (!this.onceListeners.has(event)) { this.onceListeners.set(event, new Set()); } this.onceListeners.get(event)!.add(listener as EventListener); } } // 使用 type UserEvents = { login: [userId: string]; logout: []; message: [content: string, sender: string]; }; const emitter = new SimpleEventEmitter(); emitter.on("login", (userId) => { console.log(`User ${userId} logged in`); }); emitter.once("logout", () => { console.log("User logged out (once)"); }); emitter.emit("login", "user-123"); emitter.emit("logout"); emitter.emit("logout"); // 不再触发 ```
==== 练习 3:索引签名应用 ==== 实现一个类型安全的配置管理器: ```typescript // 要求: // 1. 定义 ConfigManager 接口,支持任意字符串键 // 2. 类型安全:根据键推断值的类型 // 3. 支持 get(key), set(key, value), has(key) 方法 // 4. 使用泛型实现类型安全 ```
参考答案 ```typescript // 配置项类型映射 interface ConfigSchema { "api.url": string; "api.timeout": number; "app.debug": boolean; "app.name": string; "cache.enabled": boolean; "cache.ttl": number; } type ConfigKey = keyof ConfigSchema; // 配置管理器接口 interface ConfigManager { get(key: K): ConfigSchema[K]; set(key: K, value: ConfigSchema[K]): void; has(key: ConfigKey): boolean; getAll(): Readonly>; } // 实现 class DefaultConfigManager implements ConfigManager { private config: Partial = {}; get(key: K): ConfigSchema[K] { if (!(key in this.config)) { throw new Error(`Config key '${key}' not found`); } return this.config[key]!; } set(key: K, value: ConfigSchema[K]): void { this.config[key] = value; } has(key: ConfigKey): boolean { return key in this.config; } getAll(): Readonly> { return { ...this.config }; } } // 使用 const config = new DefaultConfigManager(); config.set("api.url", "https://api.example.com"); config.set("api.timeout", 5000); config.set("app.debug", true); const url = config.get("api.url"); // 类型: string const timeout = config.get("api.timeout"); // 类型: number console.log(config.has("api.url")); // true ```
==== 练习 4:接口继承实践 ==== 设计一个图形编辑器类型系统: ```typescript // 要求: // 1. 定义基础 Shape 接口,包含 x, y, id, render() // 2. 定义 Colorful 接口,包含 fillColor, strokeColor // 3. 定义 Movable 接口,包含 move(dx, dy), rotate(angle) // 4. 创建 Circle 接口(继承 Shape, Colorful) // 5. 创建 Rectangle 接口(继承 Shape, Colorful, Movable) // 6. 创建 Group 接口,包含 shapes 数组 ```
参考答案 ```typescript // 基础接口 interface Shape { readonly id: string; x: number; y: number; render(): void; getBounds(): { width: number; height: number }; } interface Colorful { fillColor: string; strokeColor: string; opacity?: number; } interface Movable { move(dx: number, dy: number): void; rotate(angle: number, centerX?: number, centerY?: number): void; scale(factor: number): void; } // 圆形 interface Circle extends Shape, Colorful { radius: number; } // 矩形 interface Rectangle extends Shape, Colorful, Movable { width: number; height: number; rotation?: number; } // 多边形 interface Polygon extends Shape, Colorful, Movable { points: Array<{ x: number; y: number }>; } // 组 interface Group extends Shape { readonly shapes: readonly Shape[]; addShape(shape: Shape): void; removeShape(id: string): void; getShape(id: string): Shape | undefined; } // 实现示例 class CanvasCircle implements Circle { readonly id: string; x: number; y: number; radius: number; fillColor: string; strokeColor: string; opacity: number = 1; constructor(config: Omit) { this.id = config.id; this.x = config.x; this.y = config.y; this.radius = config.radius; this.fillColor = config.fillColor; this.strokeColor = config.strokeColor; if (config.opacity !== undefined) { this.opacity = config.opacity; } } render(): void { console.log(`Rendering circle at (${this.x}, ${this.y}) with radius ${this.radius}`); } getBounds(): { width: number; height: number } { return { width: this.radius * 2, height: this.radius * 2 }; } } // 使用 const circle: Circle = new CanvasCircle({ id: "circle-1", x: 100, y: 100, radius: 50, fillColor: "red", strokeColor: "black" }); circle.render(); console.log(circle.getBounds()); ```
===== 扩展阅读 ===== - [[TypeScript:第六章_类|下一章:类]] - [[https://www.typescriptlang.org/docs/handbook/2/objects.html|TypeScript 官方文档 - 对象类型]] - [[https://www.typescriptlang.org/docs/handbook/interfaces.html|TypeScript 官方文档 - 接口]]