目录

第五章:接口

本章概述

接口(Interface)是 TypeScript 最核心的特性之一,它定义了对象的“形状”,提供了强大的类型约束能力。本章将深入讲解接口的定义、使用和各种高级特性。

5.1 接口基础

5.1.1 什么是接口

接口是对对象结构的描述,它定义了对象应该包含哪些属性和方法。

// 基本接口定义
interface Person {
  name: string;
  age: number;
}
 
// 使用接口
const alice: Person = {
  name: "Alice",
  age: 25
};
 
// 类型检查
const bob: Person = {
  name: "Bob",
  age: 30
};

5.1.2 接口与类型别名的区别

// 接口
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 必需属性

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)

使用 ? 标记可选属性:

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 标记只读属性:

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 接口定义函数类型

// 定义函数接口
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 带属性的函数

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 构造函数签名

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 字符串索引签名

// 定义字符串索引签名
interface StringDictionary {
  [key: string]: string;
}
 
const dict: StringDictionary = {
  name: "Alice",
  country: "China"
};
 
// 使用
console.log(dict["name"]);
dict["city"] = "Beijing";

5.4.2 数字索引签名

interface NumberArray {
  [index: number]: string;
}
 
const arr: NumberArray = ["a", "b", "c"];
console.log(arr[0]);  // "a"

5.4.3 混合索引签名

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 只读索引签名

interface ReadonlyDictionary {
  readonly [key: string]: number;
}
 
const readonlyDict: ReadonlyDictionary = {
  x: 10,
  y: 20
};
 
// readonlyDict["x"] = 100;  // Error

5.5 接口继承

5.5.1 单继承

interface Animal {
  name: string;
}
 
interface Dog extends Animal {
  breed: string;
}
 
const myDog: Dog = {
  name: "Buddy",
  breed: "Golden Retriever"
};

5.5.2 多继承

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 继承时的类型兼容性

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 类实现接口

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 多个接口

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 可选方法的接口

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 接口的声明合并

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 严格的属性检查

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 响应类型

// 基础响应接口
interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
  timestamp: number;
}
 
// 用户相关
interface User {
  id: number;
  name: string;
  email: string;
  avatar?: string;
}
 
interface UserResponse extends ApiResponse<User> {}
interface UsersResponse extends ApiResponse<User[]> {}
 
// 使用
async function fetchUser(id: number): Promise<UserResponse> {
  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 接口

// 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 (
    <button 
      onClick={onClick}
      disabled={disabled || loading}
      className={`btn btn-${variant} btn-${size}`}
    >
      {loading ? "Loading..." : label}
    </button>
  );
}

5.9 本章小结

本章我们学习了:

1. 接口基础 - 定义对象结构,与类型别名的区别

2. 属性类型 - 必需属性、可选属性、只读属性

3. 函数类型 - 函数签名、带属性的函数、构造函数

4. 索引签名 - 字符串和数字索引签名

5. 接口继承 - 单继承和多继承

6. 类实现接口 - implements 关键字

7. 高级特性 - 可选方法、声明合并、严格属性检查

5.10 练习题

练习 1:接口设计

设计一个完整的电商系统类型接口:

  1. Product(商品):id, name, price, category, description?, imageUrl?, inStock
  2. CartItem(购物车项):product, quantity
  3. ShoppingCart(购物车):items, totalPrice, addItem(), removeItem(), clear()
  4. Order(订单):id, items, totalAmount, status, createdAt, shippingAddress

参考答案

// 基础类型
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<T> {
  success: boolean;
  data?: T;
  error?: string;
}
 
type ProductResponse = ApiResponse<Product>;
type ProductsResponse = ApiResponse<Product[]>;
type OrderResponse = ApiResponse<Order>;

练习 2:函数接口

实现一个事件系统接口:

// 要求:
// 1. 定义 EventEmitter 接口
// 2. 支持 on(event, listener) 订阅事件
// 3. 支持 off(event, listener) 取消订阅
// 4. 支持 emit(event, ...args) 触发事件
// 5. 支持 once(event, listener) 一次性订阅

参考答案

// 事件监听器类型
type EventListener<T extends any[] = any[]> = (...args: T) => void;
 
// 事件发射器接口
interface EventEmitter {
  on<T extends any[]>(event: string, listener: EventListener<T>): void;
  off<T extends any[]>(event: string, listener: EventListener<T>): void;
  emit<T extends any[]>(event: string, ...args: T): void;
  once<T extends any[]>(event: string, listener: EventListener<T>): void;
}
 
// 实现
class SimpleEventEmitter implements EventEmitter {
  private listeners: Map<string, Set<EventListener>> = new Map();
  private onceListeners: Map<string, Set<EventListener>> = new Map();
 
  on<T extends any[]>(event: string, listener: EventListener<T>): void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event)!.add(listener as EventListener);
  }
 
  off<T extends any[]>(event: string, listener: EventListener<T>): void {
    this.listeners.get(event)?.delete(listener as EventListener);
    this.onceListeners.get(event)?.delete(listener as EventListener);
  }
 
  emit<T extends any[]>(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<T extends any[]>(event: string, listener: EventListener<T>): 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:索引签名应用

实现一个类型安全的配置管理器:

// 要求:
// 1. 定义 ConfigManager 接口,支持任意字符串键
// 2. 类型安全:根据键推断值的类型
// 3. 支持 get(key), set(key, value), has(key) 方法
// 4. 使用泛型实现类型安全

参考答案

// 配置项类型映射
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<K extends ConfigKey>(key: K): ConfigSchema[K];
  set<K extends ConfigKey>(key: K, value: ConfigSchema[K]): void;
  has(key: ConfigKey): boolean;
  getAll(): Readonly<Partial<ConfigSchema>>;
}
 
// 实现
class DefaultConfigManager implements ConfigManager {
  private config: Partial<ConfigSchema> = {};
 
  get<K extends ConfigKey>(key: K): ConfigSchema[K] {
    if (!(key in this.config)) {
      throw new Error(`Config key '${key}' not found`);
    }
    return this.config[key]!;
  }
 
  set<K extends ConfigKey>(key: K, value: ConfigSchema[K]): void {
    this.config[key] = value;
  }
 
  has(key: ConfigKey): boolean {
    return key in this.config;
  }
 
  getAll(): Readonly<Partial<ConfigSchema>> {
    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:接口继承实践

设计一个图形编辑器类型系统:

// 要求:
// 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 数组

参考答案

// 基础接口
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<Circle, "render" | "getBounds">) {
    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());

扩展阅读