組織模塊以提供你想要的API形式保持一致是比較難的。 比如,你可能想要這樣一個模塊,可以用或不用 new
來創(chuàng)建不同的類型, 在不同層級上暴露出不同的命名類型, 且模塊對象上還帶有一些屬性。
閱讀這篇指定后,你就會了解如果書寫復雜的暴露出友好API的聲明文件。 這篇指定針對于模塊(UMD)庫,因為它們的選擇具有更高的可變性。
如果你理解了一些關于TypeScript是如何工作的核心概念, 那么你就能夠為任何結構書寫聲明文件。
如果你正在閱讀這篇指南,你可能已經(jīng)大概了解TypeScript里的類型指是什么。 明確一下, 類型通過以下方式引入:
type sn = number | string;
)interface I { x: number[]; }
)class C { }
)enum E { A, B, C }
)import
聲明以上每種聲明形式都會創(chuàng)建一個新的類型名稱。
與類型相比,你可能已經(jīng)理解了什么是值。 值是運行時名字,可以在表達式里引用。 比如 let x = 5;
創(chuàng)建一個名為x
的值。
同樣,以下方式能夠創(chuàng)建值:
let
,const
,和var
聲明namespace
或module
聲明enum
聲明class
聲明import
聲明function
聲明類型可以存在于命名空間里。 比如,有這樣的聲明 let x: A.B.C
, 我們就認為 C
類型來自A.B
命名空間。
這個區(qū)別雖細微但很重要 -- 這里,A.B
不是必需的類型或值。
一個給定的名字A
,我們可以找出三種不同的意義:一個類型,一個值或一個命名空間。 要如何去解析這個名字要看它所在的上下文是怎樣的。 比如,在聲明 let m: A.A = A;
, A
首先被當做命名空間,然后做為類型名,最后是值。 這些意義最終可能會指向完全不同的聲明!
這看上去另人迷惑,但是只要我們不過度的重載這還是很方便的。 下面讓我們來看看一些有用的組合行為。
眼尖的讀者可能會注意到,比如,class
同時出現(xiàn)在類型和值列表里。 class C { }
聲明創(chuàng)建了兩個東西: 類型C
指向類的實例結構, 值C
指向類構造函數(shù)。 枚舉聲明擁有相似的行為。
假設我們寫了模塊文件foo.d.ts
:
export var SomeVar: { a: SomeType };
export interface SomeType {
count: number;
}
這樣使用它:
import * as foo from './foo';
let x: foo.SomeType = foo.SomeVar.a;
console.log(x.count);
這可以很好地工作,但是我們知道SomeType
和SomeVar
很相關 因此我們想讓他們有相同的名字。 我們可以使用組合通過相同的名字 Bar
表示這兩種不同的對象(值和對象):
export var Bar: { a: Bar };
export interface Bar {
count: number;
}
這提供了解構使用的機會:
import { Bar } from './foo';
let x: Bar = Bar.a;
console.log(x.count);
再次地,這里我們使用Bar
做為類型和值。 注意我們沒有聲明 Bar
值為Bar
類型 -- 它們是獨立的。
有一些聲明能夠通過多個聲明組合。 比如, class C { }
和interface C { }
可以同時存在并且都可以做為C
類型的屬性。
只要不產(chǎn)生沖突就是合法的。 一個普通的規(guī)則是值總是會和同名的其它值產(chǎn)生沖突除非它們在不同命名空間里, 類型沖突則發(fā)生在使用類型別名聲明的情況下( type s = string
), 命名空間永遠不會發(fā)生沖突。
讓我們看看如何使用。
interface
添加我們可以使用一個interface
往別一個interface
聲明里添加額外成員:
interface Foo {
x: number;
}
// ... elsewhere ...
interface Foo {
y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK
這同樣作用于類:
class Foo {
x: number;
}
// ... elsewhere ...
interface Foo {
y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK
注意我們不能使用接口往類型別名里添加成員(type s = string;
)
namespace
添加namespace
聲明可以用來添加新類型,值和命名空間,只要不出現(xiàn)沖突。
比如,我們可能添加靜態(tài)成員到一個類:
class C {
}
// ... elsewhere ...
namespace C {
export let x: number;
}
let y = C.x; // OK
注意在這個例子里,我們添加一個值到C
的靜態(tài)部分(它的構造函數(shù))。 這里因為我們添加了一個 值,且其它值的容器是另一個值 (類型包含于命名空間,命名空間包含于另外的命名空間)。
我們還可以給類添加一個命名空間類型:
class C {
}
// ... elsewhere ...
namespace C {
export interface D { }
}
let y: C.D; // OK
在這個例子里,直到我們寫了namespace
聲明才有了命名空間C
。 做為命名空間的 C
不會與類創(chuàng)建的值C
或類型C
相互沖突。
最后,我們可以進行不同的合并通過namespace
聲明。 Finally, we could perform many different merges usingnamespace
declarations. This isn't a particularly realistic example, but shows all sorts of interesting behavior:
namespace X {
export interface Y { }
export class Z { }
}
// ... elsewhere ...
namespace X {
export var Y: number;
export namespace Z {
export class C { }
}
}
type X = string;
在這個例子里,第一個代碼塊創(chuàng)建了以下名字與含義:
X
(因為namespace
聲明包含一個值,Z
)X
(因為namespace
聲明包含一個值,Z
)X
里的類型Y
X
里的類型Z
(類的實例結構)X
的一個屬性值Z
(類的構造函數(shù))第二個代碼塊創(chuàng)建了以下名字與含義:
Y
(number
類型),它是值X
的一個屬性Z
Z
,它是值X
的一個屬性X.Z
命名空間下的類型C
X.Z
的一個屬性值C
X
export =
或import
一個重要的原則是export
和import
聲明會導出或導入目標的所有含義。
更多建議: