TypeScript系列 进阶篇:(三) 声明合并
声明合并是将两个或两个以上的独立声明合并为一个声明,这个合并声明会拥有所有之前每个声明的特性。
[toc]
一、基础概念
在TS中,一个声明会创建一个实体,为以下三种之一:
-
命名空间:使用
namespace
来声明命名空间,命名空间的声明会创建一个命名空间和值,至于什么是命名空间,会在后续的文章中介绍; -
类型:类型的声明一般使用
interface
或type
关键字,得到一个类型,此外, class 和 enum 关键字的声明也会得到一个类型; -
值:通常使用 var , let , const 来声明一个变量,创建一个值,此外,class ,enum和 function关键字也会声明一个值。
可以看出,命名空间的声明,创建一个命名空间和一个值;变量的声明只会创建一个值;interface 和 type 的声明只会创建一个类型;而 class
和 enum
的声明,既会创建一个值,也会创建一个同名的类型。
二、合并 interface
接口
interface的合并是最简单、最常见的声明合并。主要声明多个同名的 interface ,TS就会自动将其合并。但是这些 interface 中,如果有同名的非函数字段,则该字段对应的类型也应该相同,否则会被标记为错误。
interface A {
name: string,
age: number,
}
interface A {
// 同名字段name,对应的类型也相同,
name: string,
gender: 1 | 2,
}
interface B {
name: string,
age: number,
}
interface B {
// 报错,同名字段age,对应的类型不同,分别为number和string
age: string,
gender: 1 | 2,
}
而在interface的合并中,同名的函数则会被视为同一函数的重载,且后声明的接口具有更高的优先级。
interface Cloner {
clone(animal: Animal): Animal;
}
interface Cloner {
clone(animal: Sheep): Sheep;
}
interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
}
则这三个声明会被合并为如下声明,每个声明内部的顺序不变,但是各个声明中,后来的声明,顺序却在最前面。
interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
clone(animal: Sheep): Sheep;
clone(animal: Animal): Animal;
}
但是,有一种情况会例外:当函数签名里,存在一个类型是单一的字符串型的字面量(不是联合类型的字面量)的参数时,这个签名就会被置于最前面。
// 三个独立的声明
interface Document {
createElement(tagName: any): Element;
}
interface Document {
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
}
interface Document {
createElement(tagName: string): HTMLElement;
createElement(tagName: "canvas"): HTMLCanvasElement;
}
// 会被合并为如下声明
interface Document {
createElement(tagName: "canvas"): HTMLCanvasElement;
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
createElement(tagName: string): HTMLElement;
createElement(tagName: any): Element;
}
三、合并 namespace
命名空间
和 interface 的合并类似,声明的同名namespace也会合并每个声明的成员。由于namespace会创建namespace和值,命名空间和值都会合并。
// 声明两个独立的命名空间
namespace Animals {
export class Zebra {}
}
namespace Animals {
export interface Legged {
numberOfLegs: number;
}
export class Dog {}
}
// 会被合并为:
namespace Animals {
export interface Legged {
numberOfLegs: number;
}
export class Zebra {}
export class Dog {}
}
在 namespace 的声明中,没有被 export
的成员,无法在另一个同名的namespace中获取,也无法使用namespace的名字来访问。
// name 没有被 export 导出
namespace Person {
let name = "cc"
export function getName(){
return name
}
}
namespace Person {
export function setName(str: string){
// 错误,无法获取
name = str
}
}
在上面的栗子中,由于在第一个namespace中,name没有被导出,因此在第二个namespace中,无法获取到name。只需要经过 export ,就可以在其它同名的namespace中共享该成员了。
// name 没有被 export 导出
namespace Person {
export let name = "cc"
export function getName(){
return name
}
}
namespace Person {
export function setName(str: string){
name = str
}
}
四、将 Namespaces
与 Classes
、Functions
、Enums
合并
1. 合并命名空间与类
在命名空间中导出的成员,会成为同名的class的静态成员,而没有导出的成员,则无法在被class获取。
class Album {
label: Album.AlbumLabel;
}
namespace Album {
export class AlbumLabel {};
export const name = "cc";
let age = 18;
}
Album.name // "cc"
Album.age // 报错
const album = new Album()
album.name // 报错,没有实例
2. 合并 命名空间 与 函数
命名空间中导出的成员,会成为同名函数的属性。
function logName(str: string){
console.log(str + logName.name)
}
namespace logName {
export const name = "cc"
export const age = 18
let gender = 1
}
logName("yy")
3. 合并 命名空间 和 枚举
合并命名空间和枚举,会将命名空间导出的成员作为枚举的扩充。
enum Color {
red = 1,
green = 2,
blue = 4,
}
namespace Color {
export function mixColor(colorName: string) {
if (colorName == "yellow") {
return Color.red + Color.green;
} else if (colorName == "white") {
return Color.red + Color.green + Color.blue;
} else if (colorName == "magenta") {
return Color.red + Color.blue;
} else if (colorName == "cyan") {
return Color.green + Color.blue;
}
}
export const pink = 10
}
Color.mixColor("yellow") // 3
Color.pink // 10
另外还有模块扩充和全局扩充,在这里就不介绍了。下一篇,就讲讲 枚举 Enums
吧。