编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

  • iOS开发和进阶

  • Web开发和进阶

    • README
    • HTML工具手册

    • CSS样式与布局

    • JavaScript核心

    • TypeScript入门

      • README
      • TS类型系统基础
      • TS接口与对象类型
        • 1. 案例引入
          • 1.1 被 API 变更背刺的教训
          • 1.2 interface 的初衷:一次定义,处处复用
        • 2. interface 语法全解
          • 2.1 对象类型的基础声明
          • 2.2 interface 可以描述函数类型
          • 2.3 interface 可以描述可索引类型
          • 2.4 索引签名的类型兼容规则
        • 3. type 与 interface:七组关键差异
          • 差异 1:声明合并
          • 差异 2:扩展方式
          • 差异 3:原始类型的别名
          • 差异 4:联合类型与元组
          • 差异 5:映射类型
          • 差异 6:implements 与 extends
          • 差异 7:性能
          • 选型决策树
        • 4. 对象类型的进阶技巧
          • 4.1 多余属性检查(Excess Property Checking)
          • 4.2 只读与属性的修饰符
          • 4.3 函数属性 vs 方法声明
        • 5. 索引签名的实战模式
          • 5.1 动态键名访问
          • 5.2 已知属性 + 动态属性混合
          • 5.3 数字索引 vs 字符串索引
        • 6. interface 的继承与合并
          • 6.1 extends 单继承与多继承
          • 6.2 extends 与泛型约束(第 04 篇前奏)
          • 6.3 声明合并(Declaration Merging)
        • 7. 实战案例:API 类型体系设计
          • 7.1 设计可扩展的 API 响应类型
          • 7.2 接口的多态设计
        • 8. 速查表
      • TS函数与类实战
      • TS泛型编程实战
      • TS高级类型编程
      • TS类型守卫机制
      • TS模块系统详解
      • TS工程配置实践实践
    • Vue高级进阶

    • Web工程化实践

  • Linux应用开发

  • IoT智能硬件开发

  • Apps
  • Web开发和进阶
  • TypeScript入门
杨充
2025-06-24
目录

TS接口与对象类型

# 02.TS接口与对象类型

interface 如何描述"形状"?type 与 interface 的关键差异、索引签名、接口合并——构建可复用的类型契约。

# 1. 案例引入

# 1.1 被 API 变更背刺的教训

// 后端 API 返回的用户对象
function renderUser(user: { name: string; age: number }) {
    return `${user.name}, ${user.age}`;
}

// 后来 API 增加了 role 字段,又删了 age……
renderUser({ name: "张三", age: 25, role: "admin" });  
// ❌ 对象字面量直接传入时,TS 会进行"多余属性检查"

// 再过一个月,后端的结构变成:
// { userName, userAge, userRole }
// 前端所有函数签名都要改——噩梦开始了

# 1.2 interface 的初衷:一次定义,处处复用

interface User {
    name: string;
    age: number;
}

function renderUser(user: User): string {
    return `${user.name}, ${user.age}`;
}

function updateUser(user: User): void { /* ... */ }
function deleteUser(id: string): void { /* ... */ }

// 当 API 变更时,只需修改一处 interface 定义
// 所有使用该类型的函数立刻获得类型提示

这就是 interface 的核心价值——描述对象的"形状",并成为代码中可复用的类型契约。


# 2. interface 语法全解

# 2.1 对象类型的基础声明

interface Person {
    name: string;
    age: number;
    // 可选属性
    email?: string;
    // 只读属性
    readonly id: number;
}

const p: Person = {
    name: "张三",
    age: 25,
    id: 1
};

// p.id = 2;  // ❌ 只读属性不可修改

# 2.2 interface 可以描述函数类型

// 等价于 type MyFn = (a: number, b: number) => number;
interface MyFn {
    (a: number, b: number): number;
}

const add: MyFn = (a, b) => a + b;
// a 和 b 自动推断为 number,不必重复标注

# 2.3 interface 可以描述可索引类型

// 数字索引——类似数组
interface NumberArray {
    [index: number]: string;
}
const arr: NumberArray = ["a", "b", "c"];
arr[0];  // string

// 字符串索引——类似字典
interface StringDictionary {
    [key: string]: number;
}
const dict: StringDictionary = { "apple": 1, "banana": 2 };
dict["apple"];  // number

# 2.4 索引签名的类型兼容规则

// 数字索引的返回值必须是字符串索引返回值的子类型
interface Okay {
    [index: number]: number;   // 数字索引
    [key: string]: number;     // 字符串索引(和数字索引同类型,OK)
}

interface NotOkay {
    [index: number]: string;   // 数字索引 → string
    // [key: string]: number; // ❌ 字符串索引返回值(number) 不是 string 的父类型
}
// 原因:JS 中 obj[0] 等价于 obj["0"],所以数字索引的类型必须兼容字符串索引

# 3. type 与 interface:七组关键差异

# 差异 1:声明合并

// interface 同名自动合并(Declaration Merging)
interface User {
    name: string;
}
interface User {
    age: number;
}
// 最终 User = { name: string; age: number }

// type 同名报错
// type User = { email: string };  // ❌ 重复标识符

# 差异 2:扩展方式

// interface 用 extends
interface Animal { name: string; }
interface Dog extends Animal { bark(): void; }

// type 用 &
type Cat = Animal & { meow(): void; };

// extends 会做更多检查:如果有属性冲突会报错
// & 遇到冲突属性可能产生 never

# 差异 3:原始类型的别名

// type 可以为基本类型创建别名
type ID = string;
type Name = string;

// interface 只能描述对象结构
// interface Wrong = string;  // ❌

# 差异 4:联合类型与元组

// type 支持联合和元组
type Status = "active" | "inactive";
type Point = [number, number];

// interface 不支持

# 差异 5:映射类型

// type 配合 keyof 做映射(第 05 篇详解)
type ReadonlyUser = {
    readonly [K in keyof User]: User[K];
};

// interface 做不了这种动态类型推导

# 差异 6:implements 与 extends

// 类和 interface 配合更自然
interface Serializable {
    serialize(): string;
}

// class 用 implements 实现 interface
class UserModel implements Serializable {
    name!: string;
    serialize() { return JSON.stringify(this); }
}

// 类也可以用 extends 继承类,interface 也可以 extends 类
// 但 type 不能被 implements

# 差异 7:性能

// TypeScript 官方推荐:优先使用 interface
// 原因:interface 在编译器内部有缓存优化
// type 每次遇到都要重新计算交叉类型
// 差异在大型项目(>1000 类型)中可感

# 选型决策树

你要描述什么?
├── 对象/类的结构 → interface(可合并、可继承)
├── 联合类型 → type(interface 做不到)
├── 元组 → type
├── 映射类型 → type(配合 keyof)
├── 基本类型别名 → type
└── 对外暴露的公共 API → 优先 interface

# 4. 对象类型的进阶技巧

# 4.1 多余属性检查(Excess Property Checking)

interface Config {
    url: string;
    timeout?: number;
}

// 对象字面量——严格检查
const config1: Config = {
    url: "https://api.com",
    timeout: 5000,
    // retries: 3  // ❌ 多余属性
};

// 中间变量——绕过检查
const config2 = {
    url: "https://api.com",
    timeout: 5000,
    retries: 3       // 被允许(因为 config2 类型推断为 { url: string; timeout: number; retries: number })
};
const config3: Config = config2;  // ✅ 结构兼容即可

要点:只有对象字面量直接赋值时才触发多余属性检查——这是为了防止打字错误,而不是严格的"密封"语义。

# 4.2 只读与属性的修饰符

interface Box {
    readonly width: number;
    readonly height: number;
    // 可选属性的 getter 式写法
    readonly area: number;
}

// readonly vs const 的选择:
// const → 变量声明
// readonly → 对象/interface 属性

# 4.3 函数属性 vs 方法声明

interface Handler {
    // 方法声明
    onClick(e: Event): void;

    // 函数属性
    onHover: (e: Event) => void;
}

// 关键差异:strictFunctionTypes 下
// 方法声明允许双向协变(更宽松),函数属性严格逆变(更安全)
// 推荐:日常开发用方法声明,库开发用函数属性

# 5. 索引签名的实战模式

# 5.1 动态键名访问

interface Dictionary<T> {
    [key: string]: T;
}

const cache: Dictionary<number> = {};
cache["user_1"] = Date.now();
cache["user_2"] = Date.now();
console.log(cache["user_1"]);  // number

# 5.2 已知属性 + 动态属性混合

interface Window {
    title: string;
    width: number;
    // 同时允许任意字符串属性
    [prop: string]: string | number;
}

const w: Window = {
    title: "My App",
    width: 1024,
    customProp: "hello"      // ✅
};

# 5.3 数字索引 vs 字符串索引

interface StringArray {
    [index: number]: string;    // 数字索引:返回 string
    length: number;             // 明确声明的属性
    // [key: string]: number;   // ❌ 与数字索引冲突——JS 下 arr[1] 等同于 arr["1"]
}

# 6. interface 的继承与合并

# 6.1 extends 单继承与多继承

interface Shape {
    color: string;
}

interface Circle extends Shape {
    radius: number;
}

interface Square extends Shape {
    side: number;
}

// 多继承:圆角方形
interface RoundedSquare extends Circle, Square {
    borderRadius: number;
}

// RoundedSquare 包含:color + radius + side + borderRadius

# 6.2 extends 与泛型约束(第 04 篇前奏)

// interface 泛型 + extends 约束
interface APIResponse<T> {
    data: T;
    status: number;
}

interface UserAPI extends APIResponse<User> {
    // 自动拥有 data: User + status: number
}

# 6.3 声明合并(Declaration Merging)

// TS 内置类型就是用声明合并扩展的
interface Window {
    title: string;
}
// 在另一个文件(或者同一全局作用域)
interface Window {
    myCustomProp: number;
}

// 结果:Window = { title: string; myCustomProp: number }

// 实际应用:为 Vue 实例添加 $router 类型
// interface ComponentCustomProperties {
//   $router: Router;
// }

注意:非导出的 interface 在全局合并;export interface 只在模块内有效,必须用 declare module 才能合并。


# 7. 实战案例:API 类型体系设计

# 7.1 设计可扩展的 API 响应类型

// 分三层设计:基础 → 通用 → 具体

// Layer 1:基础通用
interface BaseResponse {
    code: number;
    message: string;
}

// Layer 2:带数据的响应
interface DataResponse<T> extends BaseResponse {
    data: T;
}

// Layer 3:带分页的响应
interface PaginatedResponse<T> extends DataResponse<T[]> {
    pagination: {
        page: number;
        pageSize: number;
        total: number;
    };
}

// 使用时:
type UserListResponse = PaginatedResponse<User>;
// 自动包含:code/message/data: User[]/pagination

# 7.2 接口的多态设计

// 不同类型的通知,共享公共字段
interface Notification {
    id: string;
    createdAt: Date;
    read: boolean;
}

interface MessageNotification extends Notification {
    type: "message";
    content: string;
    senderId: string;
}

interface AlertNotification extends Notification {
    type: "alert";
    title: string;
    priority: number;
}

// 联合类型收窄(第 06 篇详解)
type AppNotification = MessageNotification | AlertNotification;

function handle(n: AppNotification) {
    if (n.type === "message") {
        console.log(n.content);   // n 收窄为 MessageNotification
    }
}

# 8. 速查表

操作 interface type
描述对象 ✅ { a: number } ✅ 同左
描述函数 ✅ 调用签名 ✅ (a:number)=>void
描述联合 ❌ ✅ A \| B
描述元组 ❌ ✅ [string,number]
基本类型别名 ❌ ✅ type ID = string
同名合并 ✅ ❌ 报错
extends / implements ✅ ❌
映射类型 ❌ ✅ { [K in keyof T] }
交叉类型 ❌ (用 extends) ✅ A & B
编译器缓存 ✅ 有缓存 ⚠️ 每次计算

一句话选型:对象优先 interface,联合/元组/映射用 type;对外 API 用 interface 以便使用者扩展。


下一篇:03.TS函数与类实战

上次更新: 2026/06/24, 12:59:24
TS类型系统基础
TS函数与类实战

← TS类型系统基础 TS函数与类实战→

最近更新
01
CSS选择器入门
06-23
02
CSS定位与层级
06-23
03
CSS盒模型详解
06-23
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式