接口(Interface)是 TypeScript 最核心的特性之一,它定义了对象的“形状”,提供了强大的类型约束能力。本章将深入讲解接口的定义、使用和各种高级特性。
接口是对对象结构的描述,它定义了对象应该包含哪些属性和方法。
// 基本接口定义
interface Person {
name: string;
age: number;
}
// 使用接口
const alice: Person = {
name: "Alice",
age: 25
};
// 类型检查
const bob: Person = {
name: "Bob",
age: 30
};
// 接口
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";
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
// };
使用 ? 标记可选属性:
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"
};
使用 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
// 定义函数接口
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
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();
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);
// 定义字符串索引签名
interface StringDictionary {
[key: string]: string;
}
const dict: StringDictionary = {
name: "Alice",
country: "China"
};
// 使用
console.log(dict["name"]);
dict["city"] = "Beijing";
interface NumberArray {
[index: number]: string;
}
const arr: NumberArray = ["a", "b", "c"];
console.log(arr[0]); // "a"
interface HybridDictionary {
[key: string]: string | number;
[index: number]: string; // 数字索引的返回值必须是字符串索引的子类型
// 预定义属性
name: string; // 必须兼容索引签名
}
const hybrid: HybridDictionary = {
name: "test",
0: "zero",
1: "one",
other: "value"
};
interface ReadonlyDictionary {
readonly [key: string]: number;
}
const readonlyDict: ReadonlyDictionary = {
x: 10,
y: 20
};
// readonlyDict["x"] = 100; // Error
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
const myDog: Dog = {
name: "Buddy",
breed: "Golden Retriever"
};
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
const square: Square = {
color: "blue",
penWidth: 5,
sideLength: 10
};
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
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) {}
}
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...");
}
}
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);
}
};
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" }
};
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);
// 基础响应接口
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
// 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>
);
}
本章我们学习了:
1. 接口基础 - 定义对象结构,与类型别名的区别
2. 属性类型 - 必需属性、可选属性、只读属性
3. 函数类型 - 函数签名、带属性的函数、构造函数
4. 索引签名 - 字符串和数字索引签名
5. 接口继承 - 单继承和多继承
6. 类实现接口 - implements 关键字
7. 高级特性 - 可选方法、声明合并、严格属性检查
设计一个完整的电商系统类型接口:
参考答案
// 基础类型
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>;
实现一个事件系统接口:
// 要求: // 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"); // 不再触发
实现一个类型安全的配置管理器:
// 要求: // 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
设计一个图形编辑器类型系统:
// 要求: // 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());