887 字
4 分钟
03 · 接口与类型别名
NOTE本篇讲 TS 描述”对象形状”的两大工具:
interface与type。掌握它们,等于掌握了 TS 描述数据结构的语法。
1. interface 定义对象形状
interface User { id: number; name: string; email: string;}
function sendEmail(user: User): void { console.log(`给 ${user.name} 发邮件`);}只要传入的对象结构匹配(有 id/name/email),就通过——TS 是结构化类型系统(鸭子类型),不看”是不是同一个类”,只看”长得像不像”。
2. 可选与只读
interface User { id: number; name: string; bio?: string; // ? 可选成员 readonly createdAt: Date; // readonly 只读,赋值后不可改}
const u: User = { id: 1, name: "Tom" }; // bio 可省略u.createdAt = new Date(); // ❌ readonly 不可重新赋值
readonly只约束编译期的重新赋值,不保证深层不可变(数组元素、对象属性仍可被改)。深层冻结要用Object.freeze或Readonly<T>(第 7 篇)。
3. 索引签名
当你不知道对象有哪些键、但知道键值类型时用索引签名:
interface StringMap { [key: string]: string;}const dict: StringMap = { a: "1", b: "2" };常见场景:配置对象、字典结构。注意:一旦有索引签名,所有显式成员的类型必须兼容该签名的值类型。
4. 函数类型接口
接口也能描述函数:
interface SearchFn { (source: string, keyword: string): boolean;}const search: SearchFn = (s, k) => s.includes(k);不过函数类型更常用 type 写(见第 4 篇),接口更偏向对象。
5. 接口继承 extends
接口可继承多个:
interface Animal { name: string;}interface Dog extends Animal { bark(): void;}// Dog = { name: string; bark(): void }6. type 类型别名
type 给任意类型起名字,不只是对象:
type ID = number | string;type Point = { x: number; y: number };type Callback = (err: Error | null, data: string) => void;type 还能配合联合、交叉、条件类型做复杂组合(后续多篇大量用到)。
7. interface vs type:怎么选
这是高频面试题,结论如下:
| 场景 | 推荐 |
|---|---|
| 描述对象/类的公开形状 | interface |
需要被类 implements | interface |
| 联合类型、元组、基本类型别名 | type |
需要交叉 &、条件、映射类型 | type |
| 想被声明合并扩展 | interface |
关键差异:
- 声明合并:同名
interface会自动合并;同名type会报错。interface Foo { a: number }interface Foo { b: string } // ✅ 合并为 { a: number; b: string } - 继承方式:interface 用
extends,type 用交叉&。
实践建议:写对象/数据结构优先 interface(可读性好、可合并);涉及联合/交叉/工具类型用 type。团队统一即可。
8. 交叉类型 &
交叉把多个类型合并成一个”全部都有”的类型:
type A = { x: number };type B = { y: string };type C = A & B; // { x: number; y: string }
const c: C = { x: 1, y: "hi" };对象交叉 = 成员并集;同名成员若类型冲突(如 x: number & x: string)会变成 never,要小心。
小结
interface描述对象形状,支持可选?、只读readonly、索引签名、继承extends、声明合并。type更通用,可表达联合/交叉/基本类型别名。- 对象结构用
interface,组合类型用type。 - 交叉
&合并成员,同名冲突会变never。
练习
- 定义
interface Product { id: number; name: string; price: number; discount?: number },写函数计算最终价。 - 用
type定义type Coordinate = { lat: number; lng: number },再用交叉&加type WithName = Coordinate & { name: string }。 - 验证声明合并:写两个同名
interface Config,观察合并结果。 - 思考
type X = { a: number } & { a: string }中a是什么类型?