首页/3

3章:接口进阶:让博客类型更强大

深入接口的高级特性

45分钟
4个学习目标

学习目标

  • 1掌握可选属性和只读属性
  • 2学会使用函数类型接口
  • 3理解接口继承和组合
  • 4用接口设计博客的复杂类型系统

上一章我们做了什么

在上一章中,我们学习了基础类型和枚举,为博客定义了PostCreatePostInputUpdatePostInput等类型。

但你可能已经发现了一些问题:

  • 怎么让某些字段变成可选的?(比如用户可以不填标签)
  • 怎么防止某些字段被修改?(比如文章ID创建后不能改)
  • 如果两个接口有很多相同的字段,怎么避免重复定义?

这一章,我们会深入学习接口的高级特性,解决这些问题。

问题:博客需要更灵活的类型

想象一下这个场景:你要创建一篇新文章,但用户可能:

  • 填了标签,也可能没填
  • 设置了状态,也可能使用默认值
  • 写了摘要,也可能让系统自动生成

如果我们用之前的接口:

interface Post {
  id: string;
  title: string;
  content: string;
  tags: string[];        // 必填?
  status: PostStatus;    // 必填?
  excerpt: string;       // 必填?
}

用户必须填写所有字段才能创建文章,这显然不合理。

解决方案:可选属性

TypeScript允许我们用 ? 标记某些属性是可选的。

可选属性:让类型更灵活

typescript
加载中...

类比:可选属性就像餐厅点餐

把接口想象成餐厅的点餐单:

主菜:________(必填)
配菜:________(可选)
饮料:________(可选)
备注:________(可选)

你必须点主菜,但配菜、饮料、备注都是可选的。不填也能下单,填了更好。

可选属性就是这个意思——用户可以选择填写,也可以跳过。

问题:有些数据不该被修改

再想象一个场景:文章创建后,它的ID应该永远不变。如果有人不小心写了这样的代码:

post.id = "new-id"; // 糟糕,文章ID被改了!

这可能导致数据库混乱、链接失效等问题。

解决方案:只读属性

TypeScript用 readonly 关键字标记属性,创建后就不能修改。

只读属性:保护重要数据

typescript
加载中...

类比:只读属性就像出生证明

只读属性就像你的出生证明上的信息:

  • 出生日期:一旦确定,永远不变
  • 出生地点:一旦确定,永远不变
  • 姓名:可以改(去派出所)

在博客系统中,idcreatedAt 就像出生日期——一旦创建,永远不变。而 titlecontent 就像姓名——可以修改。

对比:可选 vs 只读

这两个特性解决的是不同的问题:

| 特性 | 语法 | 作用 | 使用场景 | |------|------|------|----------| | 可选属性 | ? | 属性可以不存在 | 用户可能不填的字段 | | 只读属性 | readonly | 属性不能修改 | ID、创建时间等 |

组合使用:

interface Post {
  readonly id: string;    // 只读且必填
  title: string;          // 可读写且必填
  excerpt?: string;       // 可读写且可选
  readonly createdAt: Date; // 只读且必填
}

问题:怎么定义函数的类型?

在博客系统中,我们经常需要传递函数作为参数。比如:

  • 文章保存成功后的回调
  • 错误处理函数
  • 搜索过滤函数

但怎么确保传入的函数参数和返回值类型正确?

// 这个函数的参数是什么类型?返回什么?
function onPostSaved(???) {
  // ...
}

解决方案:函数类型接口

我们可以用接口来定义函数的"形状"——它接收什么参数,返回什么值。

函数类型接口

typescript
加载中...

问题:两个接口有重复字段怎么办?

假设我们要定义两种文章:

  • 普通文章:有标题、内容、标签
  • 精选文章:有标题、内容、标签、推荐语、精选图片

如果直接定义:

interface Post {
  title: string;
  content: string;
  tags: string[];
}

interface FeaturedPost {
  title: string;        // 重复!
  content: string;      // 重复!
  tags: string[];       // 重复!
  recommendation: string;
  featuredImage: string;
}

三个字段重复了!如果以后要改标题的类型,得改两处。

解决方案:接口继承

FeaturedPost 继承 Post,自动获得所有字段。

接口继承:复用已有定义

typescript
加载中...

类比:接口继承就像家族遗传

接口继承就像家族遗传:

爷爷(Post)
├── 爸爸(FeaturedPost)继承了爷爷的特征
│   ├── 孙子(创建的文章实例)继承了爸爸的特征

FeaturedPost 继承了 Post 的所有字段(title、content、tags),同时添加了自己的新字段(recommendation、featuredImage)。

这样,如果 Post 的字段变化,FeaturedPost 会自动同步。

多重继承:组合更多能力

一个接口可以继承多个接口,就像一个人可以同时继承父母双方的特征。

在博客系统中的应用:

可搜索接口 (Searchable)
├── searchKeyword: string

可评论接口 (Commentable)
├── comments: Comment[]

文章接口 (Post) 继承 Searchable 和 Commentable
├── title: string
├── content: string
├── searchKeyword (来自Searchable)
├── comments (来自Commentable)

多重继承示例

typescript
加载中...

接口 vs 类型别名

你可能会问:既然有接口,为什么还有类型别名(type)?它们有什么区别?

| 特性 | 接口 (interface) | 类型别名 (type) | |------|------------------|-----------------| | 继承 | 用 extends | 用 & (交叉类型) | | 声明合并 | 支持 | 不支持 | | 联合类型 | 不支持 | 支持 | | 原始类型别名 | 不支持 | 支持 |

建议:

  • 定义对象结构时,优先用接口
  • 需要联合类型或交叉类型时,用类型别名
  • 两者可以混用,没有绝对的对错

实战:设计博客的完整类型系统

现在让我们把学到的知识应用到博客项目中。

设计原则:

  1. 基础接口定义通用字段
  2. 专用接口继承基础接口并扩展
  3. 输入接口只包含用户需要填写的字段
  4. 用只读保护自动生成的字段
  5. 用可选让接口更灵活

博客类型系统完整设计

typescript
加载中...

常见错误

学习接口时,初学者常犯以下错误:

错误1:混淆可选属性和只读属性

// 不推荐:id应该是只读,但不是可选
interface Post {
  id?: string;  // id应该是必填的!
}

// 推荐
interface Post {
  readonly id: string;  // 必填且只读
}

错误2:忘记继承时需要实现所有字段

// 错误:FeaturedPost缺少Post的字段
const post: FeaturedPost = {
  recommendation: "推荐",
  featuredImage: "/image.jpg",
  // 缺少 title, content 等!
};

错误3:过度使用接口继承

// 不推荐:继承层次太深
interface A { a: string }
interface B extends A { b: string }
interface C extends B { c: string }
interface D extends C { d: string } // 难以维护

// 推荐:使用组合
interface D {
  a: A;
  b: B;
  c: C;
  d: string;
}

动手实践

现在轮到你了!让我们为博客系统设计更多的类型。

练习1:定义用户接口 创建一个User接口,包含:

  • readonly id: string(只读)
  • name: string(必填)
  • email: string(必填)
  • avatar?: string(可选)
  • bio?: string(可选)

练习2:定义作者接口 创建一个Author接口,继承User,并添加:

  • posts: Post[](文章列表)
  • postCount: number(文章数量)

练习3:定义评论接口 创建一个Comment接口,包含:

  • readonly id: string
  • postId: string
  • content: string
  • authorName: string
  • createdAt: Date
  • replies?: Comment[](可选的回复列表)

练习4:定义文章搜索函数类型 创建一个PostFilter函数类型:

  • 参数:post: Post, keyword: string
  • 返回值:boolean

练习参考答案

typescript
加载中...

本章小结

这一章我们学习了接口的高级特性:

1. 可选属性

  • ? 标记,属性可以不存在
  • 让接口更灵活,适应不同场景

2. 只读属性

  • readonly 标记,属性不能修改
  • 保护重要数据,如ID、创建时间

3. 函数类型接口

  • 定义函数的"形状"
  • 确保回调函数类型正确

4. 接口继承

  • extends 继承已有接口
  • 避免重复定义,代码更简洁

5. 多重继承

  • 一个接口可以继承多个接口
  • 组合不同的能力

下一章预告:

在下一章中,我们会学习函数的更多特性:

  • 函数重载
  • 剩余参数
  • 箭头函数的类型
  • 为博客编写工具函数

让我们继续吧!

交互式练习

1 / 1

接口综合练习

设计一个博客文章的完整接口

typescript
Loading...

章节测验

问题 1 / 4得分: 0 / 4

如何标记可选属性?

A
使用!标记
B
使用?标记
C
使用*标记
D
使用#标记