☰
扩展类型:在 TypeScript 中组合与继承接口、类型与类
2026/9/28 2:36:57 网站建设 项目流程
  • 文档
  • 教程

【免费下载链接】typescript-book

The Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.

项目地址:https://gitcode.com/gh_mirrors/typ/typescript-book
点击查看免费下载

本文是开源书籍 The Concise TypeScript Book(仓库根目录下的 扩展类型章节(英文原版) 及其 俄语译本)的深度解读。章节篇幅虽短,却是理解 TypeScript 类型组合机制的关键一环:它回答了"如何基于已有类型派生新类型"这一日常开发最高频的问题。读完本文,你将掌握接口的extends继承、多重继承、类型别名的交叉类型(&),以及"接口可以扩展类型、类型不能反向扩展接口"这一规则的深层原因,并能区分"扩展"与"声明合并"这对容易混淆的概念。

接口继承:用 extends 复制另一个类型的成员

在 TypeScript 中,interface可以通过extends关键字从另一个接口"复制"全部成员。原文档给出的最小示例是:

interface X { a: string; } interface Y extends X { b: string; }

这里Y不仅拥有自己声明的b: string,还从X继承a: string。也就是说,Y的最终形状等价于:

interface Y { a: string; b: string; }

从结构化类型(structural typing)的角度看,TypeScript 只关心"形状"是否满足约束:任何同时包含a: string与b: string的对象都可以赋值给Y类型的变量。接口继承是纯类型层面的操作,编译后不会产生任何运行时开销,extends只存在于类型检查阶段。

多重继承:一次扩展多个接口

interface支持从多个接口同时扩展,用逗号分隔基接口列表。原文档示例:

interface A { a: string; } interface B { b: string; } interface Y extends A, B { y: string; }

此时Y的成员是A、B与自身声明的并集,即同时拥有a、b、y三个成员。这种"多源继承"非常适合把横切的能力拆成若干小接口,再在业务接口上按需组合。注意:与类的单继承限制不同(类只能继承一个基类,详见下文),接口的多重继承是语言原生支持的。

仓库中 合并与扩展章节 将这种机制概括为:扩展(extension)指的是从已有类型或接口派生出新类型,向现有类型追加属性或方法,而无需修改原有定义。书中给出的完整示例:

interface Animal { name: string; eat(): void; } interface Bird extends Animal { sing(): void; } const dog: Bird = { name: 'Bird 1', eat() { console.log('Eating'); }, sing() { console.log('Singing'); }, };

注意这里赋给Bird类型变量的对象同时实现了name、eat与sing——这正是扩展后的完整约束。变量名dog虽与语义不符,但不影响类型检查:结构化类型只校验形状是否完备。

extends 只适用于接口与类:类型别名请使用交叉类型

原文档强调了一个关键规则:extends关键字只能用于interface和class,对type别名,组合多个类型要使用交叉类型运算符&:

type A = { a: number; }; type B = { b: number; }; type C = A & B;

C表示"同时满足 A 与 B"的类型,其成员是a: number与b: number的并集。仓库的 交叉类型章节 给出了更完整的语义描述与验证示例:

type X = { a: string; }; type Y = { b: string; }; type J = X & Y; // 交叉类型 const j: J = { a: 'a', b: 'b', };

交叉类型是"集合取交集"的类型层面实现:J类型的值必须同时具有X与Y的全部成员。相比interface extends,交叉类型还能与联合类型、映射类型、条件类型等任意type表达式自由组合,因此是类型别名场景下唯一的组合手段。在 接口与类型章节 中,还演示了交叉类型与联合类型的组合使用:

type TypeA = { name: string }; type TypeB = { age: number }; type CombinedType = TypeA & TypeB; // 交叉类型 let myCombined: CombinedType = { name: 'John', age: 25 };

接口可以扩展类型,但反过来不行

原文档给出的最后一个语法形态是:接口可以通过extends扩展一个type别名,而type无法反向"继承"接口:

type A = { a: string; }; interface B extends A { b: string; }

也就是说,interface B extends A是合法的——只要A是一个可解析的对象形状。而反过来type C = ... extends B这种写法在 TypeScript 中根本不存在,类型别名没有extends语法,只能通过&组合。

为什么会有这种不对称?从语言设计看,extends的目标(右侧)必须是一个可以静态展开、成员完全确定的对象类型。interface和class天然满足这一要求;而type别名可以是联合类型、条件类型、映射类型等复杂构造,例如type Department = 'dep-x' | 'dep-y',接口无法从联合类型"继承成员"(接口与类型章节 明确提到"an interface cannot extend a complex type like a union type")。因此规则可以概括为:

  • 右侧是可展开的对象形状(接口、类或对象类型别名)→ 左侧接口/类可用extends;
  • 右侧是任意类型表达式 → 只能用交叉类型&组合。

若确实需要以接口参与联合类型,接口与类型章节 提供了"接口的联合"写法:

interface A { x: 'x'; } interface B { y: 'y'; } type C = A | B; // 接口的联合类型

这里C是联合类型,取值要么满足A(含x: 'x'),要么满足B(含y: 'y'),这是接口本身不具备、必须借助type别名才能表达的能力。

同名成员冲突:交叉类型与接口继承的行为差异

扩展与交叉在"同名属性"上的处理并不相同,这是一个容易踩坑的细节。

交叉类型遇到同名但类型不兼容的成员时,会产生never类型:

type A = { value: string }; type B = { value: number }; type C = A & B; // C 的 value 被推断为 string & number,即 never

此时C实际上"无法满足"——不存在一个值同时是string又是number,给C类型变量赋任何对象字面量都会报错。

接口继承则不同:同名成员要求类型互相兼容,否则extends处直接报编译错误;若兼容则允许覆盖。这种"显式报错"通常比交叉类型悄悄产生never更友好。

这一差异也体现在 接口与类型章节 的对比示例中:接口用extends继承并追加成员,类型用&合并成员,两者的最终约束对象形状是一致的:

// 接口方式:extends 继承 interface A { x: string; y: number; } interface B extends A { z: string; } // 类型方式:交叉类型组合 type B2 = A & { j: string; };

扩展与合并是两回事:declaration merging

在 TypeScript 术语里,"扩展(extension)"与"合并(merging)"是两个不同的概念,合并与扩展章节 对此做了清晰区分:

  • 扩展:从已有类型/接口派生新类型,产生一个新的类型名字;
  • 合并:把同名的多个声明合并成一个定义,不产生新名字。

接口的声明合并(declaration merging)示例:

interface X { a: string; } interface X { b: number; } const person: X = { a: 'a', b: 7, };

两个X声明被合并为同时含a与b的单一接口。接口与类型章节 进一步指出:只有接口支持声明合并,type别名不支持。这在实际开发中的典型价值是:可以在不修改第三方类型定义的前提下,通过同名接口声明为其"打补丁"补充成员。需要注意的是,声明合并与extends继承虽然都能让一个接口拥有更多成员,但前者不改名、后者产生新类型名,二者语义完全不同。

类继承与接口实现:extends 的另一个舞台

extends也用于类继承。仓库 类章节 的"继承(Inheritance)"小节说明了类继承的机制:子类继承基类的属性与方法,并可添加新成员或覆写已有方法:

class Animal { name: string; constructor(name: string) { this.name = name; } speak(): void { console.log('The animal makes a sound'); } } class Dog extends Animal { breed: string; constructor(name: string, breed: string) { super(name); this.breed = breed; } speak(): void { console.log('Woof! Woof!'); } } const dog = new Dog('Max', 'Labrador'); dog.speak(); // Woof! Woof!

与接口不同,类只支持单继承(class只能extends一个基类)。当需要"从多个来源获得行为与结构"时,TypeScript 的解法是让类implements多个接口:

interface Flyable { fly(): void; } interface Swimmable { swim(): void; } class FlyingFish implements Flyable, Swimmable { fly() { console.log('Flying...'); } swim() { console.log('Swimming...'); } }

这条路径与本文主题一脉相承:接口负责定义契约与结构,extends/implements负责继承与实现,type交叉类型负责任意形状组合,三者共同构成 TypeScript 的类型扩展工具箱。

实战建议:何时用 extends、何时用 &

综合原文档与仓库相关章节,可以给出如下选型建议:

场景推荐写法依据
从多个接口派生新接口interface Y extends A, B接口天然支持多重继承
给第三方接口补充成员(不改名)同名interface声明合并仅接口支持声明合并
组合任意type表达式type C = A & Bextends不适用于类型别名
对象类型别名之间组合type C = A & B类型别名无extends语法
类的单继承class Dog extends Animal类只允许一个基类
类实现多个契约class F implements A, B接口可被多实现

需要强调的边界是:interface extends的右侧必须是可静态展开的对象形状,联合类型等复杂type无法被接口继承,此时应改用交叉类型或"接口的联合"(type C = A | B)。

相关章节与进一步阅读

扩展类型位于本书第 16 章(侧边栏label: 16. Расширение типов,英文原版为16. Extending Types)。围绕本主题,仓库还提供以下可继续深入阅读的章节:

  • 交叉类型:&运算符的完整语义与示例;
  • 接口与类型:interface与type的公共语法、联合/交叉类型;
  • 接口与类型的区别:声明合并、扩展语法差异、联合与交叉的灵活性对比;
  • 合并与扩展:merging 与 extension 的概念辨析与完整代码;
  • 类:类继承、implements多接口实现(第 55 章);
  • 目录:全书章节导航。

本文内容同时存在于 俄语译本(关联文档) 与 英文原版 中,两版示例完全一致,可作为双语对照学习。原文档的全部示例(单接口扩展、多接口扩展、类型交叉组合、接口扩展类型)均已在上文完整保留并加以延伸,可直接复制到tsc --strict环境下编译验证。

  • 文档
  • 教程

【免费下载链接】typescript-book

The Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.

项目地址:https://gitcode.com/gh_mirrors/typ/typescript-book
点击查看免费下载
上一篇:spotify-player的开源社区行为准则:营造友好环境
下一篇:WhiteRabbitNeo完全解析:13B网络安全大模型如何打造攻防兼备的AI安全专家

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询