TypeScript 开发指南 / 05 - 接口
接口
接口(Interface)是 TypeScript 中定义对象结构的核心方式,它描述了对象应该具有哪些属性和方法。
基本接口定义
// 定义接口
interface User {
id: number;
name: string;
email: string;
}
// 使用接口
const alice: User = {
id: 1,
name: "Alice",
email: "[email protected]"
};
// ❌ 缺少属性会报错
const bob: User = {
id: 2,
name: "Bob"
// 错误:缺少 email 属性
};
// ❌ 多余属性也会报错
const charlie: User = {
id: 3,
name: "Charlie",
email: "[email protected]",
age: 25 // 错误:对象字面量中不允许多余属性
};
注意:对象字面量赋值时,TypeScript 会进行多余属性检查(excess property checking)。通过变量赋值则不会。
可选属性(Optional Properties)
使用 ? 标记可选属性:
interface Config {
host: string;
port: number;
ssl?: boolean; // 可选
timeout?: number; // 可选
retries?: number; // 可选
}
// 只提供必选属性
const config1: Config = {
host: "localhost",
port: 3000
};
// 提供部分可选属性
const config2: Config = {
host: "example.com",
port: 443,
ssl: true
};
可选属性的类型
interface User {
name: string;
nickname?: string; // 类型是 string | undefined
}
function getNickname(user: User): string {
// ❌ 直接使用可能报错
// return user.nickname.toUpperCase();
// ✅ 安全检查
if (user.nickname) {
return user.nickname.toUpperCase();
}
return user.name;
}
只读属性(Readonly Properties)
使用 readonly 标记只读属性,赋值后不能修改:
interface Point {
readonly x: number;
readonly y: number;
}
const origin: Point = { x: 0, y: 0 };
origin.x = 1; // ❌ 错误:无法为只读属性 "x" 赋值
// 内置只读数组
const numbers: readonly number[] = [1, 2, 3];
numbers.push(4); // ❌ 错误:readonly 数组没有 push 方法
numbers[0] = 10; // ❌ 错误
Readonly 工具类型
interface MutableUser {
id: number;
name: string;
email: string;
}
// 将所有属性变为只读
type ReadonlyUser = Readonly<MutableUser>;
const user: ReadonlyUser = {
id: 1,
name: "Alice",
email: "[email protected]"
};
user.name = "Bob"; // ❌ 错误
索引签名(Index Signatures)
当对象的属性名不确定时,使用索引签名:
// 字符串索引签名
interface Dictionary {
[key: string]: string;
}
const dict: Dictionary = {
hello: "你好",
world: "世界"
};
dict["typescript"] = "类型脚本"; // ✅
数字索引签名
// 数字索引签名
interface StringArray {
[index: number]: string;
}
const arr: StringArray = {
0: "第一个",
1: "第二个"
};
// 等价于数组
const arr2: StringArray = ["第一个", "第二个"];
同时使用两种索引签名
interface Mixed {
[key: string]: number;
[index: number]: number; // 数字索引签名的返回值必须是字符串索引签名返回值的子类型
}
// 也可以有固定属性
interface WithFixedProps {
name: string; // 固定属性
[key: string]: string; // 索引签名(类型必须兼容固定属性)
}
注意:索引签名的类型必须兼容所有已知属性的类型。
只读索引签名
interface ReadOnlyMap {
readonly [key: string]: string;
}
const map: ReadOnlyMap = { a: "1", b: "2" };
map["c"] = "3"; // ❌ 错误
接口继承(Extending Interfaces)
使用 extends 关键字继承接口:
// 基础接口
interface Animal {
name: string;
age: number;
}
// 继承
interface Dog extends Animal {
breed: string;
bark(): void;
}
// 使用
const myDog: Dog = {
name: "Buddy",
age: 3,
breed: "Golden Retriever",
bark() {
console.log("Woof!");
}
};
多接口继承
interface HasId {
id: number;
}
interface HasTimestamps {
createdAt: Date;
updatedAt: Date;
}
interface HasSoftDelete {
deletedAt: Date | null;
isDeleted: boolean;
}
// 继承多个接口
interface BaseEntity extends HasId, HasTimestamps, HasSoftDelete {
version: number;
}
const entity: BaseEntity = {
id: 1,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
isDeleted: false,
version: 1
};
接口合并(Declaration Merging)
同名接口会自动合并:
interface Window {
innerWidth: number;
innerHeight: number;
}
// 扩展内置 Window 接口
interface Window {
myCustomProperty: string;
}
// 两个声明合并为一个
declare const window: Window;
console.log(window.innerWidth); // ✅
console.log(window.myCustomProperty); // ✅
注意:这是 TypeScript 独有的特性,常用于扩展第三方库的类型。
接口 vs 类型别名(type)
| 特性 | interface | type |
|---|---|---|
| 对象结构定义 | ✅ | ✅ |
| 继承(extends) | ✅ | 通过交叉类型 & |
| 声明合并 | ✅ | ❌ |
| 联合类型 | ❌ | ✅ |
| 元组类型 | ❌ | ✅ |
| 映射类型 | ❌ | ✅ |
| 基本类型别名 | ❌ | ✅ |
// interface 方式
interface UserInterface {
name: string;
age: number;
}
// type 方式
type UserType = {
name: string;
age: number;
};
// 联合类型只能用 type
type Status = "active" | "inactive" | "banned";
// 元组只能用 type
type Point = [number, number];
// 交叉类型只能用 type
type Enhanced = UserInterface & { role: string };
选择建议
| 场景 | 推荐 |
|---|---|
| 定义对象结构 | interface(支持声明合并) |
| 定义联合/交叉类型 | type |
| 定义函数类型 | type(更简洁) |
| 定义基本类型别名 | type |
| 扩展第三方库类型 | interface(支持声明合并) |
函数接口
// 定义函数接口
interface SearchFunction {
(source: string, query: string): boolean;
}
const search: SearchFunction = (source, query) => {
return source.includes(query);
};
// 带属性的函数接口
interface Counter {
(): number; // 可调用
count: number; // 属性
reset(): void; // 方法
}
function createCounter(): Counter {
let count = 0;
const counter = (() => ++count) as Counter;
counter.count = 0;
counter.reset = () => { count = 0; };
return counter;
}
泛型接口
// 泛型接口
interface Repository<T> {
findById(id: number): Promise<T | null>;
findAll(): Promise<T[]>;
create(entity: Omit<T, "id">): Promise<T>;
update(id: number, entity: Partial<T>): Promise<T>;
delete(id: number): Promise<boolean>;
}
// 实现泛型接口
interface User {
id: number;
name: string;
email: string;
}
class UserRepository implements Repository<User> {
async findById(id: number): Promise<User | null> {
// 查询数据库
return null;
}
async findAll(): Promise<User[]> {
return [];
}
async create(entity: Omit<User, "id">): Promise<User> {
return { id: 1, ...entity };
}
async update(id: number, entity: Partial<User>): Promise<User> {
return { id, name: "", email: "", ...entity };
}
async delete(id: number): Promise<boolean> {
return true;
}
}
业务场景:API 响应类型
// 通用 API 响应
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
// 分页响应
interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}
// 用户相关接口
interface UserProfile {
id: number;
username: string;
avatar: string;
bio?: string;
}
interface UserSettings {
theme: "light" | "dark";
language: string;
notifications: {
email: boolean;
push: boolean;
sms: boolean;
};
}
// API 函数
async function getUserProfile(
userId: number
): Promise<ApiResponse<UserProfile>> {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
async function getUserList(
page: number,
pageSize: number
): Promise<ApiResponse<PaginatedResponse<UserProfile>>> {
const response = await fetch(
`/api/users?page=${page}&pageSize=${pageSize}`
);
return response.json();
}
注意事项
- 对象字面量赋值时有严格检查——多余的属性会报错,通过变量赋值则不会
- 索引签名的类型要兼容所有属性——包括固定属性
- 优先使用
interface定义对象结构——支持声明合并和更好的错误提示 - 可选属性的类型包含
undefined——使用时需要做空值检查 readonly只在编译时检查——运行时仍然可以修改