Skip to content
第 98 / 250 章前端⏱ 10 分钟阅读

第 98 章:接口(Interface)

学习目标

  • 掌握接口的完整语法
  • 学会对象的结构化类型(Structural Typing)
  • 理解接口的继承与合并
  • 掌握接口的高级特性(索引签名、函数类型)

一、接口基础

1.1 定义与使用

typescript
interface User {
  id: number;
  name: string;
  email: string;
}

const u: User = {
  id: 1,
  name: 'Tom',
  email: 'tom@example.com'
};

1.2 与 type 的对比

typescript
// 用 interface
interface User {
  name: string;
  age: number;
}

// 用 type
type User = {
  name: string;
  age: number;
};

结果几乎一样,但 interface 有两个特殊能力:

  1. 声明合并(declaration merging)
  2. 更清晰的面向对象语义

二、可选属性与只读属性

2.1 可选属性

typescript
interface User {
  id: number;
  name: string;
  email?: string;  // 可选
}

const u1: User = { id: 1, name: 'Tom' };           // ✅
const u2: User = { id: 1, name: 'Tom', email: 'a@b.c' }; // ✅

2.2 只读属性

typescript
interface User {
  readonly id: number;
  name: string;
}

const u: User = { id: 1, name: 'Tom' };
u.name = 'Jerry';  // ✅
u.id = 2;          // ❌ Cannot assign to 'id' because it is a read-only property

2.3 完整写法

typescript
interface User {
  readonly id: number;        // 只读
  name: string;               // 必填可写
  email?: string;             // 可选
  readonly tags?: string[];   // 可选只读
}

三、索引签名

3.1 字符串索引

typescript
interface Dictionary {
  [key: string]: string;
}

const dict: Dictionary = {
  name: 'Tom',
  email: 'tom@example.com',
  // 任何 string key
};

3.2 数字索引

typescript
interface StringArray {
  [index: number]: string;
}

const arr: StringArray = ['a', 'b', 'c'];

3.3 混合(已知字段 + 任意字段)

typescript
interface User {
  name: string;                       // 已知字段
  email: string;                      // 已知字段
  [key: string]: string | number;     // 任意字段
}

const u: User = {
  name: 'Tom',
  email: 'tom@example.com',
  age: 18,
  city: 'Beijing'
};

四、接口继承

4.1 单继承

typescript
interface Animal {
  name: string;
  age: number;
}

interface Dog extends Animal {
  breed: string;  // 新增字段
}

const d: Dog = {
  name: '旺财',
  age: 3,
  breed: '柴犬'
};

4.2 多继承

typescript
interface Printable {
  print(): void;
}

interface Loggable {
  log(): void;
}

interface Document extends Printable, Loggable {
  content: string;
}

const doc: Document = {
  content: 'Hello',
  print() { console.log(this.content); },
  log() { console.log(`log: ${this.content}`); }
};

4.3 继承 type 别名

typescript
type Base = { id: number; createdAt: Date };

interface User extends Base {
  name: string;
}

4.4 继承 + 添加可选

typescript
interface BaseConfig {
  host: string;
  port: number;
}

interface DevConfig extends BaseConfig {
  debug: true;  // 收窄类型
}

interface ProdConfig extends BaseConfig {
  debug: false;
  logger: 'loki' | 'file';
}

五、声明合并(Declaration Merging)

interface 独有的特性:同名的多个 interface 自动合并。

5.1 基础合并

typescript
interface User {
  name: string;
}

interface User {
  age: number;
}

// 合并后等价于:
interface User {
  name: string;
  age: number;
}

const u: User = { name: 'Tom', age: 18 };

5.2 合并规则

typescript
interface Box {
  width: number;
}

// 后面的同名 interface 合并
interface Box {
  height: number;
}

// 等价于
interface Box {
  width: number;
  height: number;
}

// 冲突字段会报错
interface Box {
  width: string;  // ❌ Subsequent property declarations must have the same type
}

5.3 为第三方库扩展类型

typescript
// 假设已有第三方库导出 User
declare global {
  interface User {
    name: string;
  }
}

// 想给 User 加字段(不修改源码)
declare global {
  interface User {
    email: string;
  }
}
// 合并后 User 有 name + email

这是给 windowRequestResponse 等增加字段的标准做法。

六、接口描述函数

6.1 函数接口

typescript
interface GreetFunction {
  (name: string): string;
}

const greet: GreetFunction = (name) => `Hello, ${name}!`;

6.2 可调用接口(带属性)

typescript
interface Counter {
  (): number;          // 可调用
  count: number;        // 也有属性
  reset(): void;
}

const counter = Object.assign(
  function () { return counter.count++; },
  { count: 0, reset() { this.count = 0; } }
);

七、接口描述类

7.1 implements

typescript
interface Flyable {
  fly(): void;
}

interface Swimmable {
  swim(): void;
}

class Duck implements Flyable, Swimmable {
  fly(): void {
    console.log('鸭子飞');
  }
  swim(): void {
    console.log('鸭子游');
  }
}

7.2 接口 vs 抽象类

typescript
// 抽象类
abstract class Animal {
  abstract speak(): void;
  move(): void { console.log('moving'); }
}

// 接口
interface Animal {
  speak(): void;
}
特性接口抽象类
多继承✅ 可以 extends 多个❌ 单继承
实现细节❌ 不能有✅ 可以有
运行时存在❌ 编译后消失✅ 编译后存在
何时用描述"是什么"描述"怎么做"

八、结构化类型(Structural Typing)

8.1 鸭子类型

typescript
interface User {
  name: string;
  age: number;
}

const tom = { name: 'Tom', age: 18 };
const jerry = { name: 'Jerry', age: 20, hobby: 'coding' };

function greet(u: User) {
  console.log(u.name);
}

greet(tom);    // ✅
greet(jerry);  // ✅ 多了字段也接受

TS 看的是形状(字段),不是名义类型。

8.2 兼容性

typescript
interface Named {
  name: string;
}

class Person {
  name: string = 'Tom';
  age: number = 18;
}

const p = new Person();
const n: Named = p;  // ✅ 结构匹配

8.3 函数兼容性

typescript
type Handler = (a: number, b: number) => void;

function h1(a: number, b: number) {}                    // ✅
function h2(a: number, b: number, c: number) {}         // ❌ 参数多了
function h3(a: number) {}                                // ❌ 参数少了
function h4(a: number, b: string) {}                     // ❌ 参数类型不同

重要:返回值类型必须是子类型才能兼容。

九、接口组合实战

typescript
// 基础实体
interface BaseEntity {
  id: number;
  createdAt: Date;
  updatedAt: Date;
}

// 用户
interface User extends BaseEntity {
  name: string;
  email: string;
  role: 'admin' | 'user';
}

// 文章
interface Post extends BaseEntity {
  title: string;
  content: string;
  authorId: number;
}

// 评论
interface Comment extends BaseEntity {
  content: string;
  postId: number;
  authorId: number;
}

// 分页响应
interface PagedResponse<T> {
  items: T[];
  total: number;
  page: number;
  pageSize: number;
}

type UserList = PagedResponse<User>;
type PostList = PagedResponse<Post>;

十、常见错误

10.1 对象字面量多余字段

typescript
interface User { name: string }

const u: User = { name: 'Tom', age: 18 };  // ❌
// ✅ 用变量
const obj = { name: 'Tom', age: 18 };
const u: User = obj;

10.2 索引签名冲突

typescript
interface User {
  name: string;                    // 已知字段 string
  [key: string]: number;           // ❌ 索引签名 string 不兼容
}

10.3 接口作为值的导入

typescript
// ❌ 错误:接口是类型,不是值
import { User } from './types';
user.id;  // ❌ Cannot use namespace 'User' as a type

// ✅ 正确
import type { User } from './types';

十一、本章小结

要点关键
接口定义interface User { name: string }
属性必选 / 可选 ? / 只读 readonly
索引签名[key: string]: any 任意字段
继承extends 支持单继承和多继承
声明合并同名 interface 自动合并
结构化类型看形状不看名义
implements用接口约束类的方法

动手练习

  1. 基础接口:设计一个 Article 接口,包含 id / title / content / author / publishedAt
  2. 继承:让 FeaturedArticle 继承 Article,添加 featured: boolean 字段
  3. 声明合并:写两个同名 interface,验证会自动合并

推荐阅读


下一章第 99 章:枚举(Enum)

本站基于 VitePress 构建 · 由 Codebook 团队维护