学习目标
- 1掌握可选属性和只读属性
- 2学会使用函数类型接口
- 3理解接口继承和组合
- 4用接口设计博客的复杂类型系统
上一章我们做了什么
在上一章中,我们学习了基础类型和枚举,为博客定义了Post、CreatePostInput、UpdatePostInput等类型。
但你可能已经发现了一些问题:
- 怎么让某些字段变成可选的?(比如用户可以不填标签)
- 怎么防止某些字段被修改?(比如文章ID创建后不能改)
- 如果两个接口有很多相同的字段,怎么避免重复定义?
这一章,我们会深入学习接口的高级特性,解决这些问题。
问题:博客需要更灵活的类型
想象一下这个场景:你要创建一篇新文章,但用户可能:
- 填了标签,也可能没填
- 设置了状态,也可能使用默认值
- 写了摘要,也可能让系统自动生成
如果我们用之前的接口:
interface Post {
id: string;
title: string;
content: string;
tags: string[]; // 必填?
status: PostStatus; // 必填?
excerpt: string; // 必填?
}
用户必须填写所有字段才能创建文章,这显然不合理。
解决方案:可选属性
TypeScript允许我们用 ? 标记某些属性是可选的。
可选属性:让类型更灵活
类比:可选属性就像餐厅点餐
把接口想象成餐厅的点餐单:
主菜:________(必填)
配菜:________(可选)
饮料:________(可选)
备注:________(可选)
你必须点主菜,但配菜、饮料、备注都是可选的。不填也能下单,填了更好。
可选属性就是这个意思——用户可以选择填写,也可以跳过。
问题:有些数据不该被修改
再想象一个场景:文章创建后,它的ID应该永远不变。如果有人不小心写了这样的代码:
post.id = "new-id"; // 糟糕,文章ID被改了!
这可能导致数据库混乱、链接失效等问题。
解决方案:只读属性
TypeScript用 readonly 关键字标记属性,创建后就不能修改。
只读属性:保护重要数据
类比:只读属性就像出生证明
只读属性就像你的出生证明上的信息:
- 出生日期:一旦确定,永远不变
- 出生地点:一旦确定,永远不变
- 姓名:可以改(去派出所)
在博客系统中,id 和 createdAt 就像出生日期——一旦创建,永远不变。而 title 和 content 就像姓名——可以修改。
对比:可选 vs 只读
这两个特性解决的是不同的问题:
| 特性 | 语法 | 作用 | 使用场景 |
|------|------|------|----------|
| 可选属性 | ? | 属性可以不存在 | 用户可能不填的字段 |
| 只读属性 | readonly | 属性不能修改 | ID、创建时间等 |
组合使用:
interface Post {
readonly id: string; // 只读且必填
title: string; // 可读写且必填
excerpt?: string; // 可读写且可选
readonly createdAt: Date; // 只读且必填
}
问题:怎么定义函数的类型?
在博客系统中,我们经常需要传递函数作为参数。比如:
- 文章保存成功后的回调
- 错误处理函数
- 搜索过滤函数
但怎么确保传入的函数参数和返回值类型正确?
// 这个函数的参数是什么类型?返回什么?
function onPostSaved(???) {
// ...
}
解决方案:函数类型接口
我们可以用接口来定义函数的"形状"——它接收什么参数,返回什么值。
函数类型接口
问题:两个接口有重复字段怎么办?
假设我们要定义两种文章:
- 普通文章:有标题、内容、标签
- 精选文章:有标题、内容、标签、推荐语、精选图片
如果直接定义:
interface Post {
title: string;
content: string;
tags: string[];
}
interface FeaturedPost {
title: string; // 重复!
content: string; // 重复!
tags: string[]; // 重复!
recommendation: string;
featuredImage: string;
}
三个字段重复了!如果以后要改标题的类型,得改两处。
解决方案:接口继承
让 FeaturedPost 继承 Post,自动获得所有字段。
接口继承:复用已有定义
类比:接口继承就像家族遗传
接口继承就像家族遗传:
爷爷(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)
多重继承示例
接口 vs 类型别名
你可能会问:既然有接口,为什么还有类型别名(type)?它们有什么区别?
| 特性 | 接口 (interface) | 类型别名 (type) | |------|------------------|-----------------| | 继承 | 用 extends | 用 & (交叉类型) | | 声明合并 | 支持 | 不支持 | | 联合类型 | 不支持 | 支持 | | 原始类型别名 | 不支持 | 支持 |
建议:
- 定义对象结构时,优先用接口
- 需要联合类型或交叉类型时,用类型别名
- 两者可以混用,没有绝对的对错
实战:设计博客的完整类型系统
现在让我们把学到的知识应用到博客项目中。
设计原则:
- 基础接口定义通用字段
- 专用接口继承基础接口并扩展
- 输入接口只包含用户需要填写的字段
- 用只读保护自动生成的字段
- 用可选让接口更灵活
博客类型系统完整设计
常见错误
学习接口时,初学者常犯以下错误:
错误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
练习参考答案
本章小结
这一章我们学习了接口的高级特性:
1. 可选属性
- 用
?标记,属性可以不存在 - 让接口更灵活,适应不同场景
2. 只读属性
- 用
readonly标记,属性不能修改 - 保护重要数据,如ID、创建时间
3. 函数类型接口
- 定义函数的"形状"
- 确保回调函数类型正确
4. 接口继承
- 用
extends继承已有接口 - 避免重复定义,代码更简洁
5. 多重继承
- 一个接口可以继承多个接口
- 组合不同的能力
下一章预告:
在下一章中,我们会学习函数的更多特性:
- 函数重载
- 剩余参数
- 箭头函数的类型
- 为博客编写工具函数
让我们继续吧!
交互式练习
接口综合练习
设计一个博客文章的完整接口
章节测验
如何标记可选属性?