学径XUEJING · 个人学习平台通知设置

课程库 TypeScript TypeScript 起步 › 第 4

对象与接口 —— 描述数据的形状

普通课55 分钟

本课导读

真实数据很少是孤零零的一个数——订单有编号、金额、买家,用户有名字、等级、标签。对象的类型怎么写?答案是本课主角 interface(接口):给一"类"数据的形状起名签约。它是 TS 日常使用频率最高的关键字,也是你将来读任何前端/Node 代码库时满眼都是的东西。学完本课,你能为任何业务数据建模。预计 55 分钟。

从内联标注到 interface

先看"不用 interface"时对象类型怎么写,体会一下痛:

TypeScript

同一坨形状描述写了两遍——数据一改形状,处处要改。interface 的意义:给形状起个名字,一处定义,处处引用

TypeScript

interface 语法拆解:

  • interface 名字 { 字段: 类型; ... }——名字约定首字母大写(Order、User);它只存在于编译期,不产生任何运行时代码
  • 合同三重保护:缺字段(TS2741 会点名缺了谁)、字段类型错拼错属性名(附赠 Did you mean)——上一课函数签名管"边界",interface 管"数据本身"
  • 读法练习:看到 function printOrder(o: Order),你应该自动读出"这个函数收一个 Order 形状的对象"

提示

类比记忆:interface 是积木的模具图纸——图纸规定每个孔位(字段)的位置和形状(类型)。造出来的积木(对象)少个孔、孔位形状不对,装配线(编译器)当场退货。图纸本身不出现在成品里(编译后消失),但整条流水线都靠它对齐。

可选属性与 readonly

形状里也有"可有可无"和"只读不改":

TypeScript
  • note?: string 的完整含义:这个字段的类型是 string | undefined——用之前判断(下一课"收窄"会把这个动作讲成体系)
  • readonly 保护"身份类"字段(id、创建时间):初始化时给值,之后谁改谁报错——bug 少一整类

嵌套与数组:形状会组合

真实数据是嵌套的——interface 同样能层层组合,而且一个 interface 的字段可以是另一个 interface

TypeScript

两个新面孔顺手认了:Order[]——"Order 形状的数组",批量数据的标准形态;reduce((a, b) => a + b, 0)——把数组"折叠"成一个值(这里是求和,0 是起始值),和 map/filter 组成数组三件套。

结构决定一切:TS 的"认形不认名"

TS 类型系统有个重要脾气——只看形状,不看出身(术语叫结构化类型):

TypeScript

spot 从未声明自己是 Point,但它长得像(有 number 的 x 和 y)——TS 放行。这叫"鸭子类型"的静态版:走起来像鸭子、叫起来像鸭子,就当它是鸭子。实际意义:对接外部数据(比如 API 返回的 JSON)时,只要形状匹配你的 interface,就能无缝进入类型体系——不需要谁"正式声明血统"。

✍️ 练习

练习 1:为课程建模

定义 Course 接口:标题(字符串)、时长分钟数(数字)、是否免费(布尔)、讲师名(可选)。创建一免费一付费两门课(付费的带讲师),写函数 intro(c: Course) 返回一句介绍。

TypeScript
✅ 参考答案
interface Course {
    title: string;
    minutes: number;
    free: boolean;
    teacher?: string;
}

function intro(c: Course): string {
    const price = c.free ? "免费" : "付费";
    let result = "《" + c.title + "》" + c.minutes + " 分钟," + price;
    if (c.teacher !== undefined) {
        result = result + ",讲师:" + c.teacher;
    }
    return result;
}

const courseA: Course = { title: "TS 起步", minutes: 45, free: true };
const courseB: Course = { title: "泛型实战", minutes: 90, free: false, teacher: "老周" };

console.log(intro(courseA));
console.log(intro(courseB));

条件 ? A : B 是三元表达式——if/else 的单行版,读作"free 吗?是就 A 否则 B"。)

练习 2:违约三连抓

下面三个对象各违反了 Task 合同的一条。先逐个预测报错原因,再取消注释验证,最后修好它们。

TypeScript
✅ 参考答案

t2:缺 done 字段(TS2741,报错会点名 done)→ 补上 done: false。t3:done 给了字符串(TS2322)→ 改成 done: false。t1.id:readonly 字段被改(TS2540)→ 身份字段初始化定终身,需要"新 id"就造新对象。三类违约对应 interface 的三重保护——缺、错、改。

练习 3:批量订单报表(挑战)

用本课的嵌套 Order 接口:给 orders 数组再加一单(形状自拟但要合规),然后 ① 筛出上海客户的订单 ② 算全部订单总额 ③ 输出每单的"买家 - 金额"清单。全程 map/filter,不写 for。

TypeScript
✅ 参考答案
interface Customer {
    name: string;
    city: string;
}

interface Order {
    id: number;
    amount: number;
    customer: Customer;
}

const orders: Order[] = [
    { id: 1001, amount: 599, customer: { name: "小雨", city: "上海" } },
    { id: 1002, amount: 149, customer: { name: "老王", city: "成都" } },
    { id: 1003, amount: 918, customer: { name: "丽丽", city: "上海" } },
];

const shanghai = orders.filter((o) => o.customer.city === "上海");
console.log("上海订单 " + shanghai.length + " 单");

const total = orders.map((o) => o.amount).reduce((a, b) => a + b, 0);
console.log("总额 " + total + " 元");

orders.map((o) => o.customer.name + " - " + o.amount + " 元")
      .forEach((line) => console.log(line));

注意整段代码的体验:每个 o. 后面点什么,类型系统都一清二楚——嵌套再深、链再长,拼错一个字母都逃不掉。这就是"形状合同"在批量数据上的日常红利。

📝 随堂测验

随堂测验0 / 5 题正确

1. interface 相比内联对象标注,核心价值是?

2. const o: Order = { id: 1, product: "鼠标" } 缺了 amount 字段,TS 会?

3. interface 里 note?: string 的完整含义是?

4. readonly id: number 适合什么字段、防什么?

5. 对象 spot 从未声明实现 Point,却能传给收 Point 的函数,因为?

本课小结

  • interface 名字 { 字段: 类型 }:给数据形状签约——一处定义、处处引用,编译后消失
  • 三重保护:缺字段、类型错、拼错名(附 Did you mean);? 可选字段(用前判断)、readonly 身份字段锁定
  • 形状可组合:接口套接口、Order[] 批量形态;数组三件套 map/filter/reduce 全程类型护航
  • TS 认形不认名(结构化类型):形状匹配即放行——对接外部 JSON 的天然接口

下一课直面真实世界的"不确定":一个值可能是 A 也可能是 B(string | number、可能是 undefined)——联合类型与收窄,TS 最有味道的一章。

划选正文任意文字可高亮、批注或加入复习卡

讨论

载入中…