

通過(guò)結(jié)構(gòu)化模塊,為所需的 API 提供精確的原型并不是一件容易的事情。例如:我們可能需要一個(gè)模塊在被調(diào)用時(shí),帶上 new 關(guān)鍵字與不帶 new 關(guān)鍵字來(lái)生成不同的類型,在層級(jí)關(guān)系中,暴露出不同的命名,并且還在模塊對(duì)象上具有一些屬性。
通過(guò)閱讀本指南,您將擁有編寫復(fù)雜定義文件的工具,這些文件將提供友好的 API 表面。 本指南關(guān)注模塊(或 UMD)庫(kù),因?yàn)檫@里的選項(xiàng)更多。
一、關(guān)鍵概念(Key Concepts)
通過(guò)理解 TypeScript 的一些關(guān)鍵概念,您可以充分理解如何進(jìn)行任何形式的定義。
1、類型(Types)
如果您正在閱讀本指南,您可能已經(jīng)大致了解 TypeScript 中的類型。 然而,再明確一下,一種類型可以通過(guò)以下形式被引入:
- 類型別名聲明:
type sn = number | string; - 接口聲明:
interface I { x: number[]; } - 類聲明:
class C { } - 枚舉聲明:
enum E { A, B, C } -
import聲明指向一個(gè)類型
2、值(Values)
與類型一樣,您可能已經(jīng)理解了什么是 Value。 Value 是我們可以在表達(dá)式中引用的運(yùn)行時(shí)名稱。 例如:let x = 5; 創(chuàng)建一個(gè)名為 x 的 Value。
明確一下通過(guò)以下形式創(chuàng)建 Value:
-
let、const、var聲明 -
namespace或module聲明包含一個(gè) Value -
enum聲明 -
class聲明 -
import聲明指向一個(gè)值 -
function聲明
3、命名空間(Namespaces)
類型可以存在于命名空間中。例如:有 let x: A.B.C 聲明,我們說(shuō)類型 C 來(lái)自命名空間 A.B。
這種區(qū)別是微妙而重要的——在這里,A.B 不一定是必要的類型或值。
二、簡(jiǎn)單的組合:一個(gè)名字,多重含義
給定一個(gè)名稱 A,我們可以為 A 找到三種不同的含義:一個(gè)類型,一個(gè)值或一個(gè)命名空間。 名稱的解釋方式取決于其使用的上下文。 例如,在聲明 let m:A.A = A; 中,A 首先用作命名空間,然后用作類型名稱,然后用作值。 這些含義最終可能指的是完全不同的聲明!
這看起來(lái)可能會(huì)讓人困惑,但只要我們不過(guò)分使用,它實(shí)際上非常方便。 我們來(lái)看看這種組合行為的一些有用的方面。
1、內(nèi)建組合(Built-in Combinations)
精明的讀者會(huì)注意到,例如:class 同時(shí)出現(xiàn)在 type 和 value 清單中。 聲明 class C {} 創(chuàng)建了兩項(xiàng)內(nèi)容:
- 類型
C——類C的實(shí)例原型 - 值
C——類C的構(gòu)造函數(shù)
枚舉聲明的行為類似。
2、用戶組合(User Combinations)
假設(shè)我們寫了一個(gè)模塊文件 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 密切相關(guān),所以你希望它們有相同的名字。 我們可以使用組合來(lái)以相同的名稱顯示這兩個(gè)不同的對(duì)象(值和類型)Bar:
export var Bar: { a: Bar };
export interface Bar {
count: number;
}
這為消費(fèi)代碼中的解構(gòu)提供了一個(gè)非常好的機(jī)會(huì):
import { Bar } from './foo';
let x: Bar = Bar.a;
console.log(x.count);
同樣,我們?cè)谶@里使用 Bar 同時(shí)作為類型和值。 請(qǐng)注意,我們不必將 Bar的值聲明為 Bar 類型——它們是獨(dú)立的。
三、高級(jí)組合(Advanced Combinations)
某些類型的聲明可以在多個(gè)聲明中組合使用。 例如,class C { } 和 interface C { } 可以共存,并且都為 C 類型提供屬性。
只要不產(chǎn)生沖突,這是合法的。 一般的經(jīng)驗(yàn)法則是:
- 值總是與其他相同名稱的值沖突,除非它們被聲明為命名空間;
- 如果使用類型別名進(jìn)行聲明類型,如:
type s = string,則可能會(huì)產(chǎn)生沖突; - 命名空間永不沖突。
我們來(lái)看看如何使用它。
1、使用 interface 添加(Adding using an interface)
我們可以使用另一個(gè) interface 聲明將其他成員添加到現(xiàn)有 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
請(qǐng)注意,我們不能使用 interface 添加類型別名,如:type s = string;。
2、使用 namespace 添加(Adding using a namespace)
namespace 聲明可用于以任何不會(huì)產(chǎn)生沖突的方式,添加新的類型、值和命名空間。
例如,為類添加靜態(tài)成員:
class C {
}
// ... elsewhere ...
namespace C {
export let x: number;
}
let y = C.x; // OK
請(qǐng)注意,在這個(gè)例子中,我們向 C 的靜態(tài)端(它的構(gòu)造函數(shù))添加了一個(gè)值。 這是因?yàn)槲覀兲砑恿艘粋€(gè)值,并且所有值的容器都是另一個(gè)值(類型由命名空間包含,命名空間由其他命名空間包含)。
我們也可以為類添加一個(gè)命名空間類型:
class C {
}
// ... elsewhere ...
namespace C {
export interface D { }
}
let y: C.D; // OK
在這個(gè)例子中,直到我們寫了命名空間聲明之前,沒(méi)有一個(gè)命名空間 C。C 作為命名空間的含義,與該類創(chuàng)建的 C 的值或類型的含義不沖突。
最后,我們可以使用命名空間聲明來(lái)執(zhí)行許多不同的合并。 這不是一個(gè)特別實(shí)際的例子,但顯示了各種有趣的行為:
namespace X {
export interface Y { }
export class Z { }
}
// ... elsewhere ...
namespace X {
export var Y: number;
export namespace Z {
export class C { }
}
}
type X = string;
在本例中,第一個(gè)塊創(chuàng)建以下名稱含義:
- 值
X(因?yàn)?namespace聲明包含了值Z) - 命名空間
X(因?yàn)?namespace聲明包含了類型Y) - 類型
Y在命名空間X中 - 類型
Z在命名空間X中(類的實(shí)例原型) - 值
Z,屬于X值的屬性(類的構(gòu)造器)
第二個(gè)塊創(chuàng)建以下名稱含義:
- 值
Y(number類型,X值的屬性) - 命名空間
Z - 值
Z(X值的屬性) - 類型
C(在命名空間X.Z中) - 值
C(X.Z值的屬性) - 類型
X
四、使用 export = 或 import
一個(gè)重要的規(guī)則,是 export 和 import 聲明輸出或輸入它們目標(biāo)的所有含義。
五、參考資料
譯自 Definition File Theory: A Deep Dive
(完)