对象类型与接口 interface

本节目标

描述一个对象长什么样,最直观是「内联写法」:

// 运行环境:Node.js 16+ + typescript;类型检查 npx tsc --noEmit ts-l5.ts

// 内联对象类型:直接写在注解位置
function printUser(user: { name: string; age: number }): void {
  console.log(`${user.name} / ${user.age}`)
}
printUser({ name: 'Laowen', age: 30 })

但当结构要复用时,用 interface 给它起个名字:

// 用 interface 定义「用户」结构,全项目复用
interface User {
  name: string
  age: number
  email?: string      // ? 表示可选:可以有也可以没有
  readonly id: number // readonly:创建后不能改
}

const u: User = { name: 'A', age: 20, id: 1 }
// u.id = 2            // ❌ Cannot assign to 'id' because it is read-only
// const u2: User = { name: 'B', id: 2 } // ❌ 缺少必填的 age

function greet(user: User): string {
  return `Hi ${user.name}`
}

interface 和 type 大部分场景等价,区别在:interface 可以「声明合并」(同名的 interface 会自动合并字段),type 更灵活(能描述联合、元组、基础类型别名)。经验法则:描述「对象/类结构」优先 interface;做类型运算(联合、交叉、映射)用 type。

// type 能做到 interface 做不到的:
type ID = string | number          // 联合
type Point = { x: number; y: number }
type Point3D = Point & { z: number } // 交叉,合并出三维点

名词解释

接口(interface):给「对象的形状」起的名字。它只描述「这个对象应该有哪些字段、各是什么类型」,不关心值从哪来。你可以把它理解成「对象的体检表」——TS 拿它去核对每个对象合不合格。

可选属性(?)/ 只读属性(readonly):age?: number 表示「这个字段可能缺席」,访问前要判空;readonly id 表示「出生时定好、之后不能再改」,编译期保护你不被意外覆盖。它们都是从「代码意图」层面帮你少犯错。

课后练习

练习 1:定义一个 Book 接口,含 title(必填 string)、author(必填 string)、year(可选 number),并写一个函数返回书名。

答案:

interface Book {
  title: string
  author: string
  year?: number
}
function getTitle(b: Book): string { return b.title }

练习 2:interface 和 type 在描述对象时最大的「能力差异」是什么?

答案:interface 支持同名声明合并(多次声明同名字段会合并),适合被第三方扩展(如给 Window 加字段);type 能做联合、交叉、映射类型等类型运算,表达能力更强。描述纯对象结构时二者几乎等价。

总结

我的观点:interface 和 type 之争在团队里常常被过度讨论,其实90% 的场景二者随便选都不会错。我个人的默认是——凡是「描述一个东西长什么样」(对象、类)就用 interface,因为可读性更好、能被合并;凡是「做类型层面的计算」(联合、交叉、提取)才用 type。比「选哪个」更重要的是:给复用三次以上的结构起名字,别让 { name: string; age: number } 这种内联类型在代码里复制粘贴十遍——那才是真正拖慢维护的坏习惯。