TypeScript 声明合并
-
基础概念
TypeScript 中的声明会创建以下三种实体之一:命名空间,类型或值。 创建命名空间的声明会新建一个命名空间,它包含了用(.)符号来访问时使用的名字。 创建类型的声明是:用声明的模型创建一个类型并绑定到给定的名字上。 最后,创建值的声明会创建在 JavaScript 输出中看到的值。Declaration Type Namespace Type Value Namespace X X Class X X Enum X X Interface X Type Alias X Function X Variable X 理解每个声明创建了什么,有助于理解当声明合并时有哪些东西被合并了。 -
合并接口
最简单也最常见的声明合并类型是接口合并。 从根本上说,合并的机制是把双方的成员放到一个同名的接口里。接口的非函数的成员应该是唯一的。如果它们不是唯一的,那么它们必须是相同的类型。如果两个接口中同时声明了同名的非函数成员且它们的类型不同,则编译器会报错。对于函数成员,每个同名函数声明都会被当成这个函数的一个重载。 同时需要注意,当接口 A 与后来的接口 A 合并时,后面的接口具有更高的优先级。如下例所示:这三个接口合并成一个声明:注意:每组接口里的声明顺序保持不变,但各组接口之间的顺序是后来的接口重载出现在靠前位置。这个规则有一个例外是当出现特殊的函数签名时。 如果签名里有一个参数的类型是 单一的字符串字面量(比如,不是字符串字面量的联合类型),那么它将会被提升到重载列表的最顶端。比如,下面的接口会合并到一起:合并后的 Document 将会像下面这样: -
合并命名空间
与接口相似,同名的命名空间也会合并其成员。 命名空间会创建出命名空间和值,我们需要知道这两者都是怎么合并的。对于命名空间的合并,模块导出的同名接口进行合并,构成单一命名空间内含合并后的接口。对于命名空间里值的合并,如果当前已经存在给定名字的命名空间,那么后来的命名空间的导出成员会被加到已经存在的那个模块里。Animals 声明合并示例:等同于:除了这些合并外,你还需要了解非导出成员是如何处理的。 非导出成员仅在其原有的(合并前的)命名空间内可见。这就是说合并之后,从其它命名空间合并进来的成员无法访问非导出成员。下例提供了更清晰的说明:因为 haveMuscles 并没有导出,只有 animalsHaveMuscles 函数共享了原始未合并的命名空间可以访问这个变量。 doAnimalsHaveMuscles 函数虽是合并命名空间的一部分,但是访问不了未导出的成员。 -
命名空间与类和函数和枚举类型合并
命名空间可以与其它类型的声明进行合并。 只要命名空间的定义符合将要合并类型的定义。合并结果包含两者的声明类型。 TypeScript 使用这个功能去实现一些 JavaScript 里的设计模式。这让我们可以表示内部类。合并规则与上面 合并命名空间小节里讲的规则一致,我们必须导出 AlbumLabel类,好让合并的类能访问。 合并结果是一个类并带有一个内部类。 你也可以使用命名空间为类增加一些静态属性。除了内部类的模式,你在JavaScript里,创建一个函数稍后扩展它增加一些属性也是很常见的。 TypeScript使用声明合并来达到这个目的并保证类型安全。相似的,命名空间可以用来扩展枚举型: -
非法的合并
TypeScript并非允许所有的合并。 目前,类不能与其它类或变量合并。 想要了解如何模仿类的合并,请参考 TypeScript的混入。虽然 JavaScript 不支持合并,但你可以为导入的对象打补丁以更新它们。让我们考察一下这个玩具性的示例:它也可以很好地工作在 但编译器对 Observable.prototype.map 一无所知。 你可以使用扩展模块来将它告诉编译器:模块名的解析和用 import/ export 解析模块标识符的方式是一致的。 更多信息请参考 Modules。 当这些声明在扩展中合并时,就好像在原始位置被声明了一样。 但是,你不能在扩展中声明新的顶级声明-仅可以扩展模块中已经存在的声明。你也以在模块内部添加声明到全局作用域中。全局扩展与模块扩展的行为和限制是相同的。