强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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)

特性interfacetype
对象结构定义
继承(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();
}

注意事项

  1. 对象字面量赋值时有严格检查——多余的属性会报错,通过变量赋值则不会
  2. 索引签名的类型要兼容所有属性——包括固定属性
  3. 优先使用 interface 定义对象结构——支持声明合并和更好的错误提示
  4. 可选属性的类型包含 undefined——使用时需要做空值检查
  5. readonly 只在编译时检查——运行时仍然可以修改

扩展阅读